SerhiiLabs

SSO for Self-Hosted n8n with OIDC: Google Workspace, Authentik, and Keycloak

4,647 words 22 min read
Categories n8n-automation

You can wire self-hosted n8n to log in against your identity provider with OpenID Connect. Before you touch a single setting, two facts decide whether the rest of this is worth your time. First, native SSO in n8n is a paid feature, not a configuration toggle - and for OIDC specifically, n8n’s docs currently point at the Enterprise tier. Second, once you have paid for it, the three identity providers people most often reach for are not interchangeable: n8n’s current OIDC client does not implement PKCE and does not handle the state parameter, and Google Workspace cannot emit the group or role claims n8n needs to drive access from the identity provider.

This guide is written against the current n8n 2.x line. OIDC configuration from environment variables landed in n8n v2.18.0, IdP-driven role provisioning in v1.122.2, and in-n8n mapping rules in v2.19.0. At the time of writing the current stable release is 2.23.3 (published 2026-06-04). SSO behavior is moving quickly on this release line, so treat the version-sensitive parts here as a starting point and re-check the docs against your exact patch.

The work splits cleanly. Keycloak and Authentik can both produce a working login and drive n8n roles from the IdP. Google Workspace logs in fine but is structurally the weakest source, so it gets its own section and a warning.

SSO here is a license, not a setting

If you run the free Community Edition, native SSO is not available to you, registered or not. The feature is unlocked by a license key applied to your self-hosted instance, and the license is validated against n8n’s license server - the license key docs go as far as telling you to allowlist the Cloudflare IP ranges the license server sits behind, so the binary has to be able to reach it.

Which paid tier, exactly, depends on which n8n page you read, and the pages do not fully agree. The umbrella SSO documentation says SSO is available on the Business and Enterprise plans, and the pricing page lists “SSO, SAML and LDAP” under Business - note the absence of OIDC in that line. The OIDC setup page itself is narrower: “Available on Enterprise plans.” Read together, the consistent picture is that SAML-based SSO starts at Business while OIDC is an Enterprise feature.

It is easy to confuse two different “free” things here. Registering a Community Edition instance with your email gets you a free license key, but per the community edition features page that key unlocks exactly three things: folders, debug in editor, and custom execution data. SSO is not on that list. Registering the Community Edition does not get you SSO.

Verify the tier before you buy. n8n’s own pages currently disagree on the granularity: the SSO overview and the pricing page put SSO under Business, the OIDC page says Enterprise. Plan tiering and entitlements change, so confirm against the live pricing page and the actual SSO screen on your own instance before you commit budget - and if the answer matters, ask n8n sales the specific question “does Business include OIDC, or only SAML”.

State this up front in your own planning so nobody spends a day configuring Keycloak only to hit a paywall.

Decide before you build: OIDC, SAML, or the unlicensed bolt-on

n8n supports both SAML and OIDC for native SSO, and the choice has two axes. The first is price: per the docs pages above, SAML is available from the Business tier while OIDC sits on Enterprise. The second is protocol maturity in n8n: OIDC is the newer path, and it currently carries two gaps that the older SAML path does not - no PKCE and no handling of the state parameter (both covered in detail below). That makes “use OIDC” a real decision rather than a default. If your IdP and your security posture are comfortable with the trade-offs, OIDC is simpler to set up. If you want the more battle-tested flow, or the Business tier is your budget ceiling, SAML is the answer today. n8n documents both in the set up SSO section.

There is a third option that some self-hosters choose, and it deserves an honest mention rather than a silent omission. The cweagans/n8n-oidc project adds OIDC to n8n without a license by injecting routes through n8n’s external hooks system. Its README opens with exactly that framing: OIDC “without requiring an enterprise license”. It works for some people, but understand what you are taking on:

  • It is unsupported by n8n and is not part of a production-grade, vendor-backed setup.
  • It relies on n8n’s external hooks and registers its own routes (it uses the path /auth/oidc/callback, distinct from the native /rest/sso/oidc/callback), so it can break on n8n upgrades.
  • It deserves a security review before you put it in front of real users.
  • On the protocol gaps it is actually ahead of native n8n on one of the two: its hooks.js generates and verifies a state value via signed cookies, but it does not implement PKCE either.

For the rest of this guide, “OIDC” means n8n’s native, licensed OIDC.

How n8n’s OIDC works, and what it cannot do

n8n’s OIDC configuration is deliberately minimal. You give it a discovery endpoint, a client ID, and a client secret; n8n exposes a redirect URL of the form https://YOUR_N8N_HOST/rest/sso/oidc/callback; and via environment variables you can additionally set a prompt value and ACR values. That is most of the surface area. The full setup walkthrough, including provider notes for Okta and Auth0, is in the OIDC setup docs.

Two limitations govern every provider section that follows, so internalize them now.

n8n’s OIDC client does not support PKCE. Per the OIDC troubleshooting docs, providers that require PKCE may reject n8n’s authorization requests outright. The only workaround is to configure your IdP so that PKCE is not required for the n8n client. n8n states that PKCE support is planned for a future release, so re-verify this before you design around it.

n8n does not handle the OIDC state parameter. If your provider enforces state as a CSRF token and expects it echoed back, login fails with the error authorization response from the server is an error (see issue #17399 and the same troubleshooting page). You have to relax state enforcement for the n8n client. n8n says full state support is also planned, so this gap may close.

Both gaps push a security trade-off onto the IdP side: to make OIDC work you are relaxing protections that the IdP would otherwise enforce on this client. That is a real cost, and it is the reason the provider sections below all hinge on whether the IdP forces PKCE.

UI configuration versus environment variables

You can configure OIDC through Settings > SSO in the UI, or declaratively from environment variables. The environment-variable path is available from v2.18.0 and is the better fit for a reproducible, version-controlled deployment, because it removes click-ops drift between environments. The variable names come from PR #28185 and are documented in the SSO environment variables reference; the activation pattern is described in manage instance settings using environment variables. One detail the docs table omits: the docs list no default for N8N_SSO_OIDC_PROMPT, but the config schema defaults prompt to select_account (the allowed values are none, login, consent, select_account, create).

The activation pattern is the part that surprises people. SSO has a dedicated N8N_SSO_MANAGED_BY_ENV switch, default false. When it is true, n8n applies the SSO variables on every startup and locks the matching UI controls, so the environment is the source of truth and the database settings are overridden each boot. When it is false, the SSO variables are not applied even if you set them. Here is a focused compose.yaml for a single-instance n8n behind a TLS-terminating reverse proxy, with Postgres as the database and OIDC managed by the environment - the same baseline as the production setup guide. It pins n8n 2.23.3 and PostgreSQL 18.4; adjust both to the current versions when you read this.

services:
postgres:
image: postgres:18.4-alpine
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: REPLACE_WITH_STRONG_DB_PASSWORD
POSTGRES_DB: n8n
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: docker.n8n.io/n8nio/n8n:2.23.3
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "5678:5678"
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: REPLACE_WITH_STRONG_DB_PASSWORD
N8N_HOST: n8n.example.com
N8N_PROTOCOL: https
N8N_PORT: 5678
N8N_EDITOR_BASE_URL: https://n8n.example.com
WEBHOOK_URL: https://n8n.example.com
N8N_ENCRYPTION_KEY: REPLACE_WITH_STABLE_ENCRYPTION_KEY
N8N_LICENSE_ACTIVATION_KEY: REPLACE_WITH_LICENSE_ACTIVATION_KEY
N8N_SSO_MANAGED_BY_ENV: "true"
N8N_SSO_OIDC_LOGIN_ENABLED: "true"
N8N_SSO_OIDC_CLIENT_ID: REPLACE_WITH_CLIENT_ID
N8N_SSO_OIDC_CLIENT_SECRET: REPLACE_WITH_CLIENT_SECRET
N8N_SSO_OIDC_DISCOVERY_ENDPOINT: https://idp.example.com/.well-known/openid-configuration
N8N_SSO_OIDC_PROMPT: select_account
volumes:
- n8n_data:/home/node/.n8n
volumes:
postgres_data:
n8n_data:

A few notes on the values that bite people:

  • N8N_EDITOR_BASE_URL must be the public URL your users hit, not the internal container address. The OIDC setup docs call this out for load balancers and proxies, and if it is wrong the redirect URL handed to your IdP will be wrong too, which manifests as a redirect-mismatch error at login.
  • The redirect URL you register in the IdP is N8N_EDITOR_BASE_URL plus /rest/sso/oidc/callback. Get the host and protocol exactly right, including https.
  • The client secret does not have to sit in the Compose file: PR #28185 itself recommends passing it via the _FILE convention (N8N_SSO_OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc_client_secret), the same Docker-secrets pattern from the secrets-without-Vault article.
  • N8N_ENCRYPTION_KEY must be stable across restarts and replicas; it is what decrypts your stored credentials, and it has nothing to do with SSO but everything to do with not bricking your instance.
  • This is a single-instance baseline. Queue mode adds Redis (the current stable line is Redis 8.8) as the execution broker, but queue mode does not change anything about the OIDC setup, so it is left out here to keep the example focused.

To turn OIDC off again, set N8N_SSO_OIDC_LOGIN_ENABLED to false and redeploy, or set N8N_SSO_MANAGED_BY_ENV to false to hand control back to the UI. Keep this in mind; it is your kill switch, and the operations section returns to it.

Role and access provisioning: what the IdP can and cannot drive

By default, role assignment is set to “Assigned manually in n8n”, which means SSO authenticates people but you grant every role by hand inside n8n. From v1.122.2 you can instead have n8n read roles from the IdP. There are three modes, set in Settings > SSO or via N8N_SSO_USER_ROLE_PROVISIONING (enum values disabled, instance_role, instance_and_project_roles, per the env var reference):

  • Assigned manually in n8n (disabled, the default): no mapping from the IdP.
  • Instance roles via SSO (instance_role): n8n reads the user’s instance role from the IdP at login; project access stays manual.
  • Instance and project roles via SSO (instance_and_project_roles): n8n reads both from the IdP at login.

If instance roles versus project roles is fuzzy territory, the permissions deep-dive maps out both axes; the short version is that an instance role and a set of per-project roles together make up a user’s real access.

When you turn on either SSO-driven mode, you then choose where the mapping logic lives. With Map rules on your IdP, n8n reads two custom claims from a dedicated n8n scope: n8n_instance_role (a string - global:member, global:admin, or global:chatUser) and n8n_projects (a string array of <project-id>:<role> entries, for example 4K3zrg3DvlMFFTB7:editor). Per the setup docs, both claims must always be included in the ID token. With Map rules inside n8n (available from v2.19.0), you write expressions in n8n that evaluate the claims your IdP does send - n8n fetches them from the userinfo endpoint and exposes them as a $claims object - and return a role. The latter is the escape hatch for providers that cannot encode n8n-specific claims, which is exactly where Google ends up.

Two consequences follow, and both are load-bearing.

IdP-driven roles make the IdP authoritative. The docs are direct: “any access granted inside n8n that isn’t reflected in the IdP response is removed from users on their next login”, and roles are re-evaluated every time they sign in. Before you enable this, n8n makes you download two CSV files of your current access settings; keep them. This is deprovisioning as a feature, but it also means a mistake in the IdP propagates to n8n at the next login.

Just-in-time provisioning is on and permissive by default. A user who authenticates successfully through your IdP is created in n8n automatically on first login - the user-creation path in oidc.service.ee.ts creates the account with the global:member role. Crucially, this happens even in the default “Assigned manually in n8n” mode, because that setting governs how roles are assigned, not whether accounts are created. n8n is therefore not your access gate. The gate is the IdP: who is allowed to authenticate, and who is assigned the n8n application. Operators have run into exactly this - a community thread from an organization with thousands of employees asked how to turn auto-creation off, and the answer was that you cannot; you restrict access at the IdP.

That second point is the fault line that separates Google Workspace from Keycloak and Authentik. A provider that can emit custom claims can drive roles and scope access; a provider that cannot leaves you with manual assignment and a wide-open front door unless you restrict authentication at the IdP.

Keycloak: the cleanest fit

Keycloak is the lowest-friction of the three. A confidential client using the standard authorization-code flow works with n8n out of the box, because Keycloak does not enforce PKCE on a client unless you explicitly set it, and it does not force the client to send state in a way n8n cannot satisfy.

Client setup:

  • Create a client of type confidential (client authentication on), with the standard flow enabled and your n8n redirect URI registered.
  • Leave the client’s PKCE method setting blank. The server administration guide spells out what blank means: PKCE “is still possible to use, but it is not required”. Selecting S256 there would enforce PKCE for the client and break n8n until n8n ships PKCE support. In current Keycloak the setting sits on the client’s settings alongside the flow toggles; older versions had it as “Proof Key for Code Exchange Code Challenge Method” buried under the client’s Advanced settings, before PR #39631 moved it somewhere more visible in mid-2025.
  • Your discovery URL follows the documented pattern https://KEYCLOAK_HOST/realms/REALM_NAME/.well-known/openid-configuration. Note that the Quarkus distribution removed the legacy /auth path segment by default; if you are on an older build, or a deployment that restored it with --http-relative-path /auth, the segment is still there, so confirm for your installation.

For roles, add a dedicated client scope and protocol mappers that emit n8n_instance_role (one of the role strings above) and n8n_projects (the <project-id>:<role> array n8n expects), make sure the scope lands in the ID token, and configure the values on the Keycloak groups that should have n8n access.

The clean path may not stay clean. PKCE is trending toward mandatory - OAuth 2.1 requires it for authorization-code clients of every type, and Keycloak has already made the per-client PKCE control more prominent for exactly that reason (issue #30227, shipped May 2025). If a future Keycloak default or realm policy starts enforcing PKCE, this setup keeps working only if n8n has shipped PKCE support by then. Watch both projects.

Authentik: works, with property mappings for roles

Authentik also fits cleanly. Its OAuth2 provider supports all the standard OAuth 2.0 and OIDC flows, and PKCE is applied when a client sends it - the provider model has no option to require PKCE from a confidential client, so n8n’s no-PKCE authorization-code flow is accepted as-is. Its scope and property-mapping system can produce the custom claims n8n needs. The main effort is authoring the scope mapping; the rest is routine.

Provider and application setup:

  • Create an OAuth2/OIDC provider with client type confidential, set your redirect URI (a strict literal or a regex - and if you use a regex, escape the dots), and note the generated client ID and client secret.
  • Authentik’s provider has an Include claims in id_token option, on by default - the provider model describes it as being “for applications that don’t access the userinfo endpoint”. Leave it on: n8n expects the n8n_* claims in the ID token.
  • Your discovery URL follows the pattern https://AUTHENTIK_HOST/application/o/APP_SLUG/.well-known/openid-configuration, where the slug is the application’s slug, not the provider name.

For roles, create a custom scope mapping that translates Authentik group membership into n8n_instance_role and n8n_projects, and attach that scope to the provider so the values land in the token. The provider docs show exactly this pattern - an expression that emits a custom claim based on the user’s group membership - which is the whole point of choosing Authentik over Google for this job.

Google Workspace: it logs in, but it is the weakest source

Google Workspace works as a generic OIDC provider. The issuer is accounts.google.com, the standard openid, email, and profile scopes apply (that is the full scopes_supported list in Google’s discovery document), and Google’s web server flow does not require PKCE - the token exchange authenticates with the client secret. The login itself is unremarkable. The problem is what Google will and will not tell n8n.

Google Cloud console setup, using the current console layout:

  1. In the Google Cloud console, open the Google Auth Platform. Since 2024 this replaced the old “OAuth consent screen” menu and is organised into Branding, Audience, Clients, Data Access, and Verification Center pages.
  2. On the Audience page, set the user type to Internal, which “limit[s] authorization requests to members of the organization”. An outside account gets an org_internal authorization error. Internal is only available on projects associated with a Google Cloud organization - in practice, a Google Workspace or Cloud Identity org - not on a personal Gmail project.
  3. Under Clients, create an OAuth client of type Web application and register your n8n redirect URI.

Now the two structural limits.

Google does not put groups or roles in the ID token. There is no groups claim in Google’s discovery document, and tools that need Google group membership all solve it the same way, outside OIDC: HashiCorp Vault’s Google provider, for example, requires a service account with domain-wide delegation calling the Admin SDK Directory API just to fetch groups. n8n’s OIDC client cannot make those API calls. So IdP-side role mapping - the n8n scope with n8n_instance_role and n8n_projects - is impossible with Google. Your realistic options are manual role assignment in n8n, or “Map rules inside n8n” expressions over the standard claims Google does provide.

For those in-n8n rules, write them against email. It is in Google’s claims_supported list, and a rule like {{ $claims.email.endsWith('@example.com') }} can tier instance roles by domain. The hd claim - which Google documents as the way to validate that a token represents a Workspace account in a specific domain (“the absence of this claim indicates that the account does not belong to a Google hosted domain”) - is an ID-token claim that is absent from Google’s claims_supported list, and n8n evaluates $claims from the userinfo response. So before writing a rule against hd, do what n8n’s own docs tell you to do anyway: inspect the actual response your tenant returns and write expressions against what is really there.

One more boundary to be clear about: mapping rules assign roles, they do not refuse logins. The default condition always assigns some role, and by the time rules run, just-in-time provisioning has already created the account.

Access scoping leans entirely on the consent screen. Google’s auth request does accept an hd parameter, but Google’s own docs are blunt about it: “Don’t rely on this UI optimization to control who can access your app, as client-side requests can be modified.” And n8n could not send it anyway - its OIDC configuration exposes only the discovery endpoint, client credentials, prompt, and ACR values, so there is no field to pass provider-specific authorization parameters (inferred from that documented surface rather than a Google-specific statement in n8n’s docs). That leaves the Internal consent screen as your real boundary. Combine a misconfigured Google client with the permissive just-in-time provisioning described earlier and you have the easiest way in this whole guide to silently admit accounts you never intended to.

The short version: Google logs people in, but it cannot drive n8n roles and it cannot scope access from within n8n. If centralized role-driven access is the reason you are doing SSO, Google is the wrong source, and Keycloak or Authentik is the right one.

Operations and the lock-out you will not see coming

SSO changes your failure modes, and the most important one is self-inflicted. The point of failure is no longer just “is n8n up” but “is the IdP reachable and is the license valid”.

Keep break-glass access. Retain a local owner or admin account and do not move to an SSO-only posture without a tested way back in. There is a documented failure where, when an n8n license expires with SSO enabled, n8n does not fall back to local login and can lock everyone out - including the owner from a fresh browser session (issue #18673, and issue #19907, filed in protest after the first was closed). Both issues are closed, so whether the exact behavior persists in your version is something to test - but the lesson stands either way: do not bet sole access on the license server and the IdP both staying healthy. Your break-glass procedure is the kill switch from earlier, setting N8N_SSO_OIDC_LOGIN_ENABLED=false (or N8N_SSO_MANAGED_BY_ENV=false) and restarting to restore local login.

Understand the difference between OIDC being “Activated” and an enforced SSO-only login, and confirm on your own instance which one you are in before you rely on it. Test the license-expiry behavior in a staging instance rather than discovering it in production.

Monitor the things that can cut off access:

  • License server reachability. Auto-renewal is on by default; per the license environment variables, with auto-renew disabled you must renew manually every 10 days, and “failure to renew the license will disable all licensed features” - SSO included. The license server sits behind Cloudflare, and n8n’s docs tell you to allowlist the Cloudflare IP ranges so the instance can always reach it. In multi-instance or autoscaling setups, also know that N8N_LICENSE_DETACH_FLOATING_ON_SHUTDOWN defaults to true, releasing floating entitlements back to the pool on shutdown; n8n recommends false for production instances that must keep their features.
  • Secret and certificate expiry on both sides: the client secret in your IdP and any TLS certificates in the path.
  • IdP availability. An IdP outage blocks every new login, so as sessions expire it degrades into a lockout. Under SSO-driven provisioning the IdP is also re-evaluated on each login, so its health is now part of your access path, permanently.

Deprovisioning through the IdP works at the role level: change a user’s group and their n8n roles change at their next login, because roles re-evaluate on every sign-in. Removing the user at the IdP stops future logins, but it does not delete the n8n account - n8n’s SAML user management page documents the same gap for SAML: remove them from n8n as well.

Verification checklist and decision matrix

Two things in this guide are most likely to be stale by the time you read it: the exact license tier for OIDC, and whether the PKCE and state limitations still hold. Re-verify both against primary sources before you build a runbook on top of this.

ProviderPKCE enforced by defaultCustom claims for role mappingDomain or access restrictionOverall friction
KeycloakNo (per-client PKCE method, blank by default)Yes, via client scope and protocol mappersRealm and client plus app assignmentLowest
AuthentikNo (applied when the client sends it; no require option on the provider)Yes, via scope and property mappingsApplication policy and group bindingsLow
Google WorkspaceNo (not required for the web server flow)No (groups and roles absent from the ID token)Internal consent screen onlyHighest accidental-exposure risk

Before you go live, walk this list:

  • Re-confirm the license tier for OIDC. As of this writing n8n’s OIDC page says Enterprise while the SSO overview and pricing page say Business and Enterprise for SSO at large - check the live pricing page and the SSO screen of your own instance.
  • Re-confirm whether the PKCE and state limitations still apply in your n8n release.
  • Confirm the discovery URL pattern for your specific IdP version (especially the Keycloak /auth path and the Authentik application slug).
  • Confirm the exact running n8n patch and that the N8N_SSO_OIDC_* variable names have not changed.
  • Verify break-glass: prove you can recover local login with OIDC disabled, before you enforce SSO.