Authio docs

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 composes user.name as $first $last unless an explicit user.name mapping 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 of owner, admin, member, viewer; unknown values fall back to member.
  • 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 standard sub claim.
  • $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_name regardless of which OID the IdP picks.
  • Plain string (no $ prefix) — treated as a literal claim key. Useful for OIDC userinfo claims like preferred_username or 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

  1. The user’s browser POSTs the SAML response to authio_sso’s ACS endpoint (or completes the OIDC callback dance).
  2. authio_sso validates signatures, audience, and replay, then flattens every attribute into a claims map keyed by the canonical $assertion.Attribute[name] shape.
  3. The SSO service calls POST /internal/sessions/from-sso on authio_auth-core with the full claims map + the connection id.
  4. Auth-core loads the connection’s attribute_map and resolves each entry against the claims. Unmapped fields fall back to the legacy hardcoded extraction.
  5. 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.