Skip to content

Custom OIDC provider discards non-standard claims — CustomClaims passthrough exists only for Keycloak #2694

Description

@mikaelha

What happens

A Custom OIDC provider (custom:<id>) drops every non-standard claim from the id_token before writing identity_data. Only these survive:

aud, email_verified, exp, family_name, given_name, iat, iss, name, phone_verified, sub

Why it matters

For Swedish BankID via a broker, the national identity number arrives as an ssn claim. It is the entire reason for using BankID in a healthcare context — it binds a clinician's login to their verified identity for record signing under Swedish Patientdatalagen. It never reaches the application.

The failure is silent. The login succeeds, the identity links, the user lands on the dashboard, and the claim is simply absent. Nothing in the API distinguishes "the IdP sent no claim" from "GoTrue dropped it" — we ran a production integration for a day believing it worked before querying the database.

The claim is definitely sent

Confirmed two independent ways:

  1. By the IdP (Idura, formerly Criipto), with a decoded id_token from their own sample flow:
{
  "identityscheme": "sebankid",
  "authenticationtype": "urn:grn:authn:se:bankid:another-device:qr",
  "nameidentifier": "cf338be6b7ac45cd898cda5787fe8c87",
  "sub": "{cf338be6-b7ac-45cd-898c-da5787fe8c87}",
  "sessionindex": "6787af23-ef72-44ab-809a-1c22020af6a9",
  "ssn": "199005152393",
  "name": "Jan Aagaard Meier",
  "givenname": "Jan Aagaard",
  "given_name": "Jan Aagaard",
  "surname": "Meier",
  "family_name": "Meier",
  "ipaddress": "185.157.134.134",
  "country": "SE"
}
  1. By us in production, calling the IdP's UserInfo endpoint with session.provider_token from exchangeCodeForSession. ssn is present, along with identityscheme, nameidentifier, sessionindex, ipaddress and country — none of which reached identity_data for the same login.

Scope configuration is not the cause

Three configurations, each with a fresh unlink and relink so identity_data was rewritten:

IdP scope strategy Scope GoTrue forwards ssn in identity_data
Static, openid openid no
Static, openid ssn openid no
Dynamic, openid ssn openid ssn no

The scope leaving GoTrue was verified by inspecting the 302 from /auth/v1/authorize?provider=custom:<id>.

The fix already exists in this repo — just not for this provider

#1917 added RawClaimscustomClaimsUserProvidedData.Metadata.CustomClaims to internal/api/provider/keycloak.go. The same treatment for the Custom OIDC provider would resolve this, and the precedent for whether such claims should be preserved is already settled by that PR being merged.

The Keycloak slot is not a usable workaround for non-Keycloak IdPs: it builds {url}/protocol/openid-connect/{auth,token,userinfo} and takes a realm base rather than a discovery document, so it 404s on the authorize hop.

Current workaround, for anyone hitting this

Take session.provider_token from exchangeCodeForSession and call the IdP's UserInfo endpoint directly. This works — it is what we ship today. But it depends on provider_token being returned for Custom OIDC, which is observed behaviour rather than documented, and it adds a network hop to every login.

Suggestion

Either port the keycloak.go passthrough to the Custom OIDC provider, or — if preserving arbitrary claims by default is unwanted — make it opt-in per provider, so integrators can name the claims they need. Silently discarding them with no signal is the part that costs the most.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions