Use case — Claims request parameter
What is the claims parameter?
By default, the RP picks claims via scopes: scope=openid profile email asks for the bundle of claims OIDC Core 1.0 §5.4 maps to those scopes (name, given_name, ..., email, email_verified). That bundle is fixed.
OIDC Core 1.0 §5.5 adds a finer mechanism: the RP sends a claims=... JSON object listing individual claims (e.g. just email_verified and phone_number) and where they should appear (in the id_token, in /userinfo, or both), with optional essential markers and value constraints.
This is most useful for two cases:
- Step-up authentication — RP asks for an essential
acrvalue that the current session does not satisfy, prompting re-authentication. - Privacy-minimising RPs — RP asks for only the claims it needs, not the full scope bundle.
The two mechanisms differ in where their output lands. In the authorization code flow, the standard profile, email, address, and phone scopes request claims from /userinfo; a claims member requests only the claims it names, at the destination it specifies. In an ID Token-only response with no access token, scope-derived claims instead appear in the ID Token.
claims["userinfo"] is not requested for the ID Token.essential does not change that routing. For ordinary claims it signals importance to the OP's authorization experience but does not require an error when the claim is unavailable. An essential acr request with a required value has special matching rules and can trigger re-authentication.
Specs referenced on this page
- OpenID Connect Core 1.0 — §5.4 (scope-to-claim mapping), §5.5 (claims request)
- RFC 9396 — Authorization Details (a structured alternative to scopes)
- RFC 9101 — JAR (when the claims request rides inside a signed request object)
Quick refresher
- Scope — a coarse permission bundle (
profile,email, …). One scope maps to a fixed set of claims. - Claim request — a fine-grained, per-claim ask. The RP specifies exactly which claims it wants and where they should appear (
id_tokenvsuserinfo), with optionalessentialmarkers. - Essential vs voluntary — for an ordinary claim,
essentialmarks its importance but does not make absence an error. The OP may omit an unavailable claim;acrwithessential: trueand a required value has special matching rules (see MFA / step-up).
Source:
examples/61-claims-request
Wiring
op.New(
/* required options */
op.WithClaimsSupported(
"sub", "iss", "aud", "exp", "iat",
"email", "email_verified",
"name", "given_name", "family_name",
"locale", "zoneinfo",
),
)claims_parameter_supported defaults to true. Pass op.WithClaimsParameterSupported(false) to remove the advertisement and ignore valid claims payloads. Scope-derived claims continue to work; malformed JSON is still rejected at the request boundary.
The discovery document then advertises:
{
"claims_parameter_supported": true,
"claims_supported": ["sub", "iss", "aud", "exp", "iat", "email", "email_verified", "name", "given_name", "family_name", "locale", "zoneinfo"]
}Driving it
CLAIMS='{"id_token":{"email":{"essential":true}},"userinfo":{"locale":null}}'
curl -G --data-urlencode "claims=$CLAIMS" \
--data-urlencode 'response_type=code' \
--data-urlencode 'client_id=demo' \
--data-urlencode 'redirect_uri=http://localhost:5173/callback' \
--data-urlencode 'scope=openid' \
--data-urlencode 'code_challenge_method=S256' \
--data-urlencode "code_challenge=$CHALLENGE" \
http://localhost:8080/oidc/authAfter the flow:
| Place | Outcome |
|---|---|
id_token | email is included if the user store has it; if not, the OP omits it without failing the request |
/userinfo response | locale is included on a best-effort basis (voluntary) |
Essential vs voluntary
For ordinary claims (anything other than acr), essential communicates importance to the authorization experience. OIDC Core 1.0 §5.5.1 says the OP must not generate an error merely because an essential claim cannot be returned, unless a claim-specific rule says otherwise. This library omits an unavailable ordinary claim.
{"essential": true}— the OP looks the claim up and includes it if the value exists; if the user store doesn't carry the claim, the OP silently omits it, exactly as it would for a voluntary request. There is no error code and no re-prompt tied to a missing ordinary claim.{"essential": false}ornull— the OP includes the claim if it has it; otherwise silently omits.
The one claim where essential has real teeth is acr. An essential acr request ({"id_token":{"acr":{"essential":true,"values":[...]}}}) that the current session's authentication context doesn't satisfy forces re-authentication — interaction_required under prompt=none, or an interactive login redirect otherwise — via the RFC 9470 step-up path. See MFA / step-up for that mechanism.
Exact value constraints
value and values are exact JSON constraints, not hints or coercions:
valuerequires the candidate claim to be JSON-equal to that one value.valuesrequires the candidate claim to be JSON-equal to at least one member of the list; it is membership, not a range.- When both are present, both constraints must pass.
- Numbers compare by exact numeric value across
json.Numberand the Go integer types a user store may return (int,int64,uint64, and the other sized integer types). Integral values above 2^53 are compared as integers rather than throughfloat64, so adjacent large integers cannot become equal through rounding; a floating-point store value uses its actual floating-point value. Strings and booleans use their ordinary JSON equality.
If the candidate does not satisfy the constraint, the requested claim is omitted under the normal best-effort projection rules.
updated_at
When selected for the id_token or /userinfo, updated_at is sourced from store.User.UpdatedAt as Unix seconds. A non-zero UpdatedAt therefore produces the same value in both objects for the same subject and grant; an explicit updated_at in the user Claims map takes precedence, and a zero timestamp does not synthesize the claim.
With JAR
When JAR is enabled, the claims JSON goes inside the request object:
{
"iss": "client",
"aud": "https://op.example.com",
"client_id": "demo",
"response_type": "code",
"redirect_uri": "https://rp.example.com/callback",
"scope": "openid",
"claims": {"id_token": {"email": {"essential": true}}}
}The library parses both shapes — query parameter and JAR-embedded — through the same merge path.
Silent authorization and an existing projection
When a returning subject is authorised silently (prompt=none) against an existing grant, a claims parameter on that request updates the projection stored on the grant before the new authorization code is issued. The subsequent ID Token and UserInfo response therefore reflect the claims requested by the silent authorization, even when the earlier grant carried a different projection. If the request omits claims, the existing projection is preserved; omission means "leave it as agreed", not "erase it".
When claims support is disabled, the OP silently suppresses requested-claim projection from existing grants. That applies to both ID Tokens and UserInfo, including tokens descended from refresh rotation; claims released by scopes are unaffected, and the grant is not rewritten, re-prompted, or failed. Re-enabling the option restores projection for grants that still carry their validated request.
Authorization Details (RFC 9396)
The claims parameter narrows which claims land in the ID Token / /userinfo. Its structured sibling, RFC 9396 authorization_details, describes what the access token may do at a resource server. The two compose on the same merge path — the library distinguishes the JSON array (authorization_details) from the JSON object (claims) by shape — but authorization_details is only accepted once you register the accepted types. See Rich authorization requests.
Read next
- Tokens primer — what claims live where.
- Rich authorization requests — the structured
authorization_detailssibling. - FAPI 2.0 Baseline — JAR + claims combined.