Concepts
SAML / OIDC attribute mapping
Map your IdP's claim names to Authio user and membership fields at runtime — without code changes on either side.
When a user signs in via SAML or OIDC, Authio needs to know which IdP claim carries the user’s email, name, role, etc. Attribute mapping is the per-connection JSON object that tells Authio “use urn:oid:2.5.4.42 as the first name, the SAML Role attribute as the membership role”.
Shipped as part of G6 in the 2026-05-21 Daylight audit (commit history under the g6- series). Before this lift, attribute_map was stored on the connection but never read at sign-in — auth-core extracted email + name from a fixed list of well-known claim names. This page documents the new runtime-applied behaviour.
Where to edit it
Open the dashboard, navigate to Organizations → [org name] → SSO connections, expand the “Attribute mapping” section on the connection row. The editor renders one input per supported Authio field; any field you leave blank inherits the legacy hardcoded extraction (NameID → email, givenName + sn → name, etc.).
For API-driven configuration, PATCH the connection through the Management API or the dashboard session API:
PATCH /v1/session/orgs/{orgId}/sso-connections/{connectionId}
Content-Type: application/json
Authorization: Bearer <session-jwt>
{
"attribute_map": {
"user.email": "$assertion.NameID",
"user.first_name": "$assertion.Attribute[urn:oid:2.5.4.42]",
"user.last_name": "$assertion.Attribute[urn:oid:2.5.4.4]",
"membership.role": "$assertion.Attribute[Role]"
}
}Mapping syntax
Authio fields (left-hand side)
Keys must live in a closed namespace. Anything else is rejected with invalid_attribute_map_key.
user.email— the address the user signs in with. Authio treats matches as case-insensitive.user.first_name,user.last_name— when both resolve, Authio composesuser.nameas$first $lastunless an explicituser.namemapping is supplied.user.name— explicit display name; wins over the first / last composition.user.avatar_url— absolute URL; rendered in the dashboard and SDK helpers.membership.role— the role assigned to the JIT-provisioned membership. Must resolve to one ofowner,admin,member,viewer; unknown values fall back tomember.org.slug,org.external_id— reserved for the “pick org from claim” flow (Phase 2). Stored today, ignored by the runtime.
Claim-path expressions (right-hand side)
$assertion.NameID— the SAML<Subject>/<NameID>value. For OIDC connections, the standardsubclaim.$assertion.Attribute[name]— the value of the named<saml:Attribute Name="name">. Case-sensitive; use the exact OID / URN / friendly name your IdP emits.$assertion.<shorthand>— Authio flattens common attributes (email, first_name, last_name) under their short-form names so you can write$assertion.first_nameregardless of which OID the IdP picks.- Plain string (no
$prefix) — treated as a literal claim key. Useful for OIDC userinfo claims likepreferred_usernameor flattened claim names.
IdP-specific patterns
Okta
{
"user.email": "$assertion.NameID",
"user.first_name": "$assertion.Attribute[urn:oid:2.5.4.42]",
"user.last_name": "$assertion.Attribute[urn:oid:2.5.4.4]",
"membership.role": "$assertion.Attribute[Role]"
}Microsoft Entra ID (Azure AD)
{
"user.email": "$assertion.NameID",
"user.first_name": "$assertion.Attribute[http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname]",
"user.last_name": "$assertion.Attribute[http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname]",
"membership.role": "$assertion.Attribute[http://schemas.microsoft.com/ws/2008/06/identity/claims/role]"
}Google Workspace
Google emits OIDs urn:oid:2.5.4.42 / urn:oid:2.5.4.4 for given / surname. Email lands on NameID by default.
{
"user.email": "$assertion.NameID",
"user.first_name": "$assertion.first_name",
"user.last_name": "$assertion.last_name"
}OneLogin
{
"user.email": "$assertion.NameID",
"user.first_name": "$assertion.Attribute[FirstName]",
"user.last_name": "$assertion.Attribute[LastName]",
"membership.role": "$assertion.Attribute[Group]"
}What happens at sign-in
- The user’s browser POSTs the SAML response to
authio_sso’s ACS endpoint (or completes the OIDC callback dance). authio_ssovalidates signatures, audience, and replay, then flattens every attribute into a claims map keyed by the canonical$assertion.Attribute[name]shape.- The SSO service calls
POST /internal/sessions/from-ssoonauthio_auth-corewith the full claims map + the connection id. - Auth-core loads the connection’s
attribute_mapand resolves each entry against the claims. Unmapped fields fall back to the legacy hardcoded extraction. - The mapped email / name / role are used to create-or-update the Authio user, mint a session, and pivot it into the connection’s organization.
Cache: the connection row is read on every sign-in (no in-process cache). A dashboard save propagates within the next sign-in; there is no “wait 60s” window like the org-policy resolver. Trade-off accepted because SAML sign-in is not in the hot-rate-limited path.
Validation
Both the management-API validator and the auth-core resolver enforce the closed key namespace. The dashboard editor renders only the supported fields, so you can’t accidentally save a typo. If you POST a body with an unknown key directly to the API, you get a 422 with invalid_attribute_map_key and the offending key named explicitly.
Circular references ("user.email": "user.email") are rejected to catch the common copy-paste mistake of pasting the Authio field name into the expression box.
Multi-valued attributes
SAML memberOf and other group / list attributes come through as arrays. Today the resolver pulls the first value — safe default that matches the legacy “single role” assumption. Multi-value mapping (e.g. “assign all memberOf groups as FGA relations”) is a Phase 2 follow-up tracked under the same G6 issue.
