OIDC external hook
Custom OIDC authentication for n8n is implemented in external-hooks/src/api/hooks.ts and related helpers/routes.
This hook adds a full OIDC sign-in flow on top of n8n external hooks, including:
- OIDC discovery or manually configured endpoints.
- Authorization code login flow.
- Just-in-time user provisioning.
- Role sync from OIDC claims.
- Frontend sign-in customization to expose an SSO button.
Source layout
| Path | Role |
|---|---|
external-hooks/src/api/hooks.ts | Serves the OIDC frontend assets under /assets. |
external-hooks/src/api/routes/oidc.ts | Registers the OIDC login and callback routes. |
external-hooks/src/api/helpers/oidc-provider.ts | Shared OIDC discovery, PKCE, token exchange, and identity flow. |
external-hooks/src/api/helpers/n8n-oidc.ts | n8n-specific OIDC config, cookies, and role helpers. |
external-hooks/src/api/assets/oidc-frontend-hook.js | Standalone browser script for the sign-in page. |
external-hooks/src/api/utils/logger.ts | Structured request, response, and error logging helpers. |
Routes
1. Start login
- URL:
GET /rest/auth/oidc/login - Implementation: registered in
external-hooks/src/api/routes/oidc.ts - Behavior:
- Fetches the OIDC discovery document, unless endpoints are provided directly by environment variables.
- Generates
stateandnoncevalues. - Stores both values in signed cookies.
- Redirects the browser to the provider authorization endpoint.
2. OIDC callback
- URL:
GET /rest/auth/oidc/callback - Implementation: registered in
external-hooks/src/api/routes/oidc.ts - Behavior:
- Validates
stateand provider errors. - Exchanges the authorization code for tokens.
- Verifies the
noncefrom the ID token when present. - Fetches user claims from the
userinfoendpoint, with fallback to ID token claims. - Resolves the n8n user by email, creates the user if needed, syncs role, then issues the n8n auth cookie.
- Redirects to
/on success or back to/signin?error=...on failure.
3. Frontend helper script
- URL:
GET /assets/oidc-frontend-hook.js - Behavior:
- Injects an SSO button into the sign-in page.
- Hides the normal email/password form by default.
- Allows fallback to the normal form with
?showLogin=true. - Implementation: served as a static file from
external-hooks/src/api/assets.
Environment variables
Required
OIDC_CLIENT_IDOIDC_CLIENT_SECRET
Discovery mode
OIDC_ISSUER
If OIDC_ISSUER is set, the hook resolves provider metadata from:
/.well-known/openid-configuration
Manual endpoint mode
When OIDC_ISSUER is not set, all of the following must be provided:
OIDC_AUTHORIZATION_ENDPOINTOIDC_TOKEN_ENDPOINTOIDC_USERINFO_ENDPOINTOIDC_JWKS_URI
Optional
-
OIDC_SCOPESDefault:openid email profile -
OIDC_ROLES_CLAIMDefault:roles -
OIDC_END_SESSION_ENDPOINTWhenOIDC_ISSUERis not set, this provides the IDP logout URL for upstream single-logout. If not set, logout redirects directly without IDP involvement. -
SSO_RESTRICT_NO_ROLEWhentrue, users without a mapped OIDC role cannot be newly provisioned, and existing users are synced to an empty role when the token carries no valid role.
Authentication flow
- User opens
/rest/auth/oidc/login. - The hook creates signed
stateandnoncecookies. - The browser is redirected to the OIDC provider.
- The provider returns to
/rest/auth/oidc/callbackwith an authorization code. - The hook exchanges the code for tokens.
- The hook loads user claims from
userinfoor falls back to the ID token. - The hook validates
emailand resolves the user in n8n. - The hook provisions or updates the user role.
- The hook signs the n8n auth token using n8n
JwtServiceand sets then8n-authcookie.
User provisioning
The callback uses email as the primary identity key.
- If the user already exists, the hook reuses that account.
- If the user does not exist, the hook creates the user with
User.createUserWithProject(...)so the personal project is created at the same time. - A random password is generated for provisioned users. It is not intended for password-based login.
Default role assignment during creation:
- First user in the system:
global:owner - Later users:
global:member - If a valid OIDC role exists, it overrides the default above.
Role mapping and sync
The hook reads roles from the configured claim:
- Source claim:
userInfo[OIDC_ROLES_CLAIM] - Parsing: comma-separated string
- Accepted values:
global:owner,global:admin,global:member - Only the first valid role is used
Examples:
roles: "global:admin"->global:adminroles: "global:member,other-role"->global:member- Missing or unsupported role -> empty role mapping
Role sync behavior:
- If
SSO_RESTRICT_NO_ROLE=false, the next role becomes the mapped OIDC role, orglobal:memberwhen the token has no valid role. - If
SSO_RESTRICT_NO_ROLE=true, the next role becomes the mapped OIDC role, or an empty string when the token has no valid role. - A role change is applied only when the current and next roles differ.
SSO_RESTRICT_NO_ROLE behavior
When SSO_RESTRICT_NO_ROLE=true:
- New users are not created if no valid OIDC role is present.
- Existing users are still allowed to authenticate.
- Existing users are synced to an empty role if the token has no valid role.
Last owner protection
The hook prevents OIDC role sync from demoting the last global:owner in the system.
Before changing a user from global:owner to any other role:
- It counts other users that still have
global:owner. - If no other owner exists, the role change is blocked.
- The user is redirected back to sign-in with an error message instead of changing the role.
This prevents accidental lockout of the instance through upstream role changes.
Frontend integration
The hook also modifies frontend settings through the frontend.settings external hook:
frontendSettings.sso.oidc.loginEnabled = truefrontendSettings.sso.oidc.loginUrl = '/rest/auth/oidc/login'frontendSettings.sso.oidc.callbackUrl = OIDC_REDIRECT_URIfrontendSettings.userManagement.authenticationMethod = 'oidc'frontendSettings.enterprise.oidc = true
This makes the frontend treat OIDC as the primary authentication method.
Security notes
stateandnonceare stored in signed cookies to mitigate CSRF and replay attacks.- Cookie signing is derived from
N8N_ENCRYPTION_KEYwhen available, otherwise falls back toOIDC_CLIENT_SECRET. - The hook validates that the resolved user has a syntactically valid email address before provisioning.
- The hook decodes JWTs for claim extraction but does not implement signature verification itself; token trust relies on the provider token exchange flow.
Operational notes
- If required environment variables are missing, the hook logs a warning and does not register OIDC routes.
- Discovery results are cached in memory for one hour.
- Callback failures are surfaced to the UI through
/signin?error=.... - Logging uses the shared logger helpers from
external-hooks/src/api/utils/logger.ts.