SSO claim mapping
Audience: TokenTimer Enterprise system administrators tuning the attribute / claim paths used by the SAML and OIDC strategies.
TokenTimer needs four pieces of data from your IdP on every SSO login:
| Claim | Used for |
|---|---|
| Subject (stable ID) | Persistent identity link in external_identities |
| Account creation, account lookup, account-link guard | |
| Display name | Friendly name in the UI |
| Groups | Resolution against SSO group to role mappings |
Each one is configured as a comma-separated list of paths in
SSO Providers under Claim mapping (or bootstrap claim_mapping on the
provider row). Paths are tried in order; the first
non-empty value wins. Dot notation walks into nested objects
(name.givenName, _json.x-hasura-allowed-roles). Numeric segments
index into arrays (emails.0.value).
How resolution works
For each path:
- The strategy looks up
profile._json[path]first (the raw IdP-emitted payload from the validated ID Token / UserInfo). - Falls back to
profile[path](the normalised passport profile). - Direct-hit before dot-walking, so SAML attributes named with dots
(
urn:oid:0.9.2342.19200300.100.1.3) work without quoting. - Empty / null values fall through to the next path.
For groups, the strategy uses extractAll which:
- Flattens arrays.
- Splits comma- and semicolon-separated strings.
- Trims and lower-case-dedupes the result.
Defaults
If you leave a field blank, TokenTimer falls back to broad, widely supported defaults.
| Provider | Claim | Default path list |
|---|---|---|
| SAML | email,nameID,urn:oid:0.9.2342.19200300.100.1.3,http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress | |
| SAML | displayName | displayName,urn:oid:2.16.840.1.113730.3.1.241,http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name |
| SAML | groups | groups,Groups,http://schemas.xmlsoap.org/claims/Group,http://schemas.microsoft.com/ws/2008/06/identity/claims/role,memberOf |
| SAML | subject | nameID |
| OIDC | email,emails.0.value | |
| OIDC | displayName | displayName,name,name.givenName |
| OIDC | groups | groups,roles,cognito:groups |
| OIDC | subject | sub,oid,id |
Per-IdP examples
Full setup guides (redirect URIs, IdP consoles, env vars): SSO identity provider guides.
Azure AD / Microsoft Entra ID (OIDC)
Email: email
Display name: name
Groups: groups
Subject: oid # stable across email rename
Notes: Azure emits group object IDs by default. If your mappings use
display names, switch the token configuration to "emit groups as group
names" or add a groupMembershipClaims to the manifest.
Setup: Microsoft Entra ID (OIDC and SAML).
Okta (OIDC / SAML)
OIDC:
Email: email
Display name: name
Groups: groups
Subject: sub
SAML:
Email: email,urn:oid:0.9.2342.19200300.100.1.3
Display name: displayName
Groups: groups
Subject: nameID
Okta typically uses groups as an attribute name when you map a "Groups"
assertion in the SAML profile.
Setup: Okta.
Cognito (OIDC)
Email: email
Display name: name
Groups: cognito:groups
Subject: sub
The cognito:groups claim is namespaced; do not strip the prefix.
Setup: Amazon Cognito.
Keycloak (OIDC)
Email: email
Display name: name
Groups: groups,realm_access.roles
Subject: sub
realm_access.roles walks into the nested object emitted by Keycloak's
default mapper.
For realm/client creation, redirect URIs, PKCE, and group mappers, see Keycloak.
Google Workspace (OIDC)
Email: email
Display name: name
Groups: groups # only if you bind a custom claim mapping
Subject: sub
Google does not emit groups out of the box. You'll need an admin SDK
sync or a Google-side claim transformation to populate groups.
Setup: Google Workspace.
Auth0 (OIDC)
Email: email
Display name: name
Groups: groups # requires Login Action / custom claim
Subject: sub
Setup: Auth0.
AD FS (SAML)
Email: email,http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
Display name: displayName,http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
Groups: groups,http://schemas.xmlsoap.org/claims/Group
Subject: nameID
Setup: AD FS.
Subject choice
Should I key on
Always key on a stable subject when the IdP exposes one. Email is mutable;
a rename in the IdP turns into a "new user" event with TokenTimer if email
is the identity key. With a stable subject, the rename updates
users.email on the existing account and tokens, memberships, and audit
trail follow the same user.
If your IdP does not emit a stable subject (rare; even SAML 2.0 NameID
covers this when not using transient), leave the subject path empty
and TokenTimer falls back to email-based matching. The trade-off is
that an IdP-side email rename then creates a new account; see the
Identity model for the full picture.
Cross-provider merge
When a user first logs in via Okta SAML and later via Azure OIDC with the same email, TokenTimer:
- Looks up Entra OIDC
(provider_slug='entra', subject_id=<oid>), which misses. - Falls back to email match, hitting the user created via Okta SAML.
- Inserts a new
external_identitiesrow linking the OIDC subject to the existing user.
Both providers now point at the same users.id. See the
Identity model for the full lifecycle.
GitOps field locks
Per-provider claim paths can be locked via bootstrap manifest or SSO Providers.
GitOps deployments may also set SSO_<SLUG>_* env vars for core provider fields
(see the Configuration reference).
Example bootstrap claim_mapping on an OIDC provider:
"claim_mapping": {
"email": "email,emails.0.value",
"display_name": "name",
"groups": "groups,roles,realm_access.roles",
"subject": "sub"
}
Example SAML provider:
"claim_mapping": {
"email": "email,nameID",
"display_name": "displayName",
"groups": "groups,memberOf",
"subject": "nameID"
}
Precedence: env field lock > database (provider row) > default.
OIDC discovery
On first contact with the IdP, TokenTimer fetches the OpenID Connect
discovery document at {issuer}/.well-known/openid-configuration
(from the provider row) and populates the authorization, token, userinfo, and JWKS endpoints
from it. The document is cached in-process for 5 minutes.
Precedence per endpoint URL:
- Explicit override from SSO Providers advanced fields on the provider row.
- The discovery document at
{issuer}/.well-known/openid-configuration. - Issuer-shaped fallback when discovery is unavailable: Keycloak realms use
/protocol/openid-connect/*; other issuers use Auth0-shaped paths (/authorize,/oauth/token,/userinfo). Use the provider wizard to prefill overrides for non-standard IdPs (GitLab, ZITADEL, Google, etc.).
To disable the discovery fetch entirely (offline IdPs, isolated networks), disable discovery on the provider and provide endpoint overrides (or rely on step 3 fallbacks where applicable).
At strategy build or reload time the OIDC config records discoverySource as
discovery, manual, issuer_endpoint_fallback, or fallback. Admins can use
Test in SSO Providers or GET /api/admin/sso/oidc/diagnostics?slug=<slug>
on the API pod under test.
Deep links after SSO
Both /auth/saml/<slug>?next=/dashboard/tokens/123 and
/auth/oidc/<slug>?next=/dashboard/tokens/123 land the user on the requested
path after a successful flow. The path is validated as a same-origin
pathname before being passed to the IdP:
- Must start with
/and not//. - Must not contain a URL scheme.
- Max 512 chars.
Anything else falls back to /dashboard. SAML uses the standard
RelayState parameter; OIDC stashes the path on the session.