Expression-based SSO role mapping now available

SSO role provisioning now supports expression-based mapping, letting admins write claim evaluation rules instead of relying on fixed claim names.
SSO role provisioning in n8n previously mapped identity provider claims directly to n8n roles — a straightforward but inflexible approach that required exact claim name matches. Expression-based role mapping introduces a more powerful alternative: admins can write evaluation expressions that examine the full claims payload and assign roles conditionally.
When enabled via the new scopesUseExpressionMapping config flag (and the environment variable N8N_ENV_FEAT_ROLE_MAPPING_STRATEGY), the provisioning pipeline evaluates all persisted mapping rules against $claims on every SSO login. Matching rules determine both instance roles and project memberships. Stale project access is revoked when no rule assigns it.
A bug was also fixed: previously, project access revocation was skipped when expression mapping resolved to zero valid assignments. Existing project memberships are now always checked first, ensuring users are removed from projects when rules no longer match them.
The two provisioning strategies are mutually exclusive — the config API rejects attempts to enable both simultaneously. This prevents ambiguous role resolution from the same login flow.
This work completes the IAM-395 feature end-to-end, wiring expression-based mapping into both OIDC and SAML SSO flows.
View Original GitHub Description
Summary
Integrates expression-based role mapping into the SSO provisioning pipeline for both OIDC and SAML, completing the IAM-395 feature end-to-end.
Background: two provisioning strategies
n8n's SSO provisioning pipeline maps identity provider claims to n8n roles on every login. This PR adds a second strategy alongside the existing one:
Direct-claim (existing)
- Config:
scopesProvisionInstanceRole/scopesProvisionProjectRoles - Instance role: value of the
scopesInstanceRoleClaimNameclaim - Project roles: array value of
scopesProjectsRolesClaimNameclaim (projectId:rolestrings) - Always active when the config enables it
Expression-mapping (new)
- Config:
scopesUseExpressionMapping: true - Instance role: first matching
type: 'instance'rule evaluated against all claims - Project roles: all matching
type: 'project'rules, each scoped to specific projects - Also requires
N8N_ENV_FEAT_ROLE_MAPPING_STRATEGY=true
The two strategies are mutually exclusive — PATCH /provisioning/config rejects the request if scopesUseExpressionMapping is set alongside direct-claim fields.
What this PR does
New config field: scopesUseExpressionMapping (boolean, default false). When true, the expression-mapping path runs instead of the direct-claim path on every SSO login.
OIDC login flow with expression mapping enabled:
- User authenticates via OIDC
OidcService.applySsoProvisioningcallsisExpressionMappingEnabled()- If enabled: builds a
RoleResolverContextfrom token claims + userInfo viabuildOidcClaimsContext, then callsprovisionExpressionMappedRolesForUser ProvisioningServiceloads all persisted mapping rules, evaluates each expression against$claims, applies matching instance role and project memberships, and revokes any project access not present in the result
SAML login flow with expression mapping enabled:
- User authenticates via SAML
SamlService.applySsoProvisioningcallsisExpressionMappingEnabled()- If enabled: builds a
RoleResolverContextfrom raw SAML attributes viabuildSamlClaimsContext, then callsprovisionExpressionMappedRolesForUser - Same provisioning logic as OIDC from this point
Bug fixed: applyExpressionMappedProjectRoles had two early returns that skipped project access revocation when the resolved role map was empty or all mapped projects were invalid. Existing project memberships are now always fetched first and stale access is revoked, even when expression mapping resolves to zero valid assignments.
Manual testing
Prerequisites: an n8n instance with an SSO provider configured (OIDC or SAML), with N8N_ENV_FEAT_ROLE_MAPPING_STRATEGY=true set in the environment, and at least two team projects created.
Scenario 1 — Expression mapping provisions instance role (OIDC or SAML)
- Create a mapping rule: type
instance, roleglobal:admin, expression{{ $claims.department === 'engineering' }} - Enable expression mapping:
PATCH /api/v1/provisioning/configwith{ "scopesUseExpressionMapping": true } - Log in via SSO as a user whose IdP profile has
department = engineering - Expected: user's n8n instance role is
global:admin - Log in again with a user whose
departmentdiffers - Expected: user receives the default member role (no rule matched)
Scenario 2 — Expression mapping provisions project membership (OIDC or SAML)
- Create a mapping rule: type
project, roleproject:editor, expression{{ $claims.groups.includes('n8n-editors') }}, scoped to Project A - Enable expression mapping as above
- Log in via SSO as a user whose IdP profile has
groups = ['n8n-editors', 'devops'] - Expected: user is added to Project A as editor
- Remove
n8n-editorsfrom the user's groups in the IdP and log in again - Expected: user is removed from Project A (stale access revoked)
Scenario 3 — Expression mapping revokes all project access when no rules match
- Ensure the user from Scenario 2 is a member of Project A
- Delete the mapping rule (or change the expression so it no longer matches)
- Log in via SSO
- Expected: user is removed from Project A entirely — no stale membership preserved
Scenario 4 — Direct-claim path is unaffected
- Disable expression mapping:
scopesUseExpressionMapping: false, enablescopesProvisionInstanceRole: true - Log in via SSO with a claim
n8n_instance_role: global:admin - Expected: direct-claim provisioning works as before; expression mapping is not invoked
Scenario 5 — Mutual exclusivity enforced
PATCH /api/v1/provisioning/configwith bothscopesUseExpressionMapping: trueandscopesProvisionInstanceRole: true- Expected:
400 Bad Request— "Expression-based mapping and direct-claim provisioning cannot both be enabled at the same time."
Related Linear tickets
https://linear.app/n8n/issue/IAM-395
Review / Merge checklist
- PR title and summary are descriptive. (conventions)
- Docs updated or follow-up ticket created.
- Tests included.
- PR Labeled with
Backport to Beta,Backport to Stable, orBackport to v1(if the PR is an urgent fix that needs to be backported)