Skip to content

Custom Claims

Custom claims let an admin attach arbitrary name / value pairs to a user. When the custom_claims scope is granted, those pairs are merged into the user’s ID token, access token, and UserInfo response. Use them to carry application-specific attributes (tenant, tier, department, entitlements) that Autentico does not model natively.

When the custom_claims scope is requested and granted, every custom claim assigned to the user is included in:

  • Access tokens – as string values
  • ID tokens – as string values
  • UserInfo responses – as string fields

Example token claims:

{
"sub": "user-123",
"tier": "gold",
"region": "eu",
"scope": "openid custom_claims"
}

Every custom claim value is emitted as a JSON string – "tier": "gold", never "tier": 3 or "active": true. Typed values (numbers, booleans, arrays) are not supported in this version.

A custom claim may not shadow a registered claim. Any name in the IANA “JSON Web Token Claims” registry is rejected on write with 400 invalid_request — this covers the RFC 7519 claims (iss, sub, exp, …), the OIDC standard claims (name, email, address, …), and security- and authorization-relevant claims Autentico does not itself emit (cnf, roles, entitlements, act, authorization_details, verified_claims, …). Transport-specific entries (SIP, CDNI) are not reserved.

As a second layer, even if a reserved name is inserted directly into the database, the token builder never lets a custom claim overwrite a claim it has already set.

Claim names must match ^[a-zA-Z_][a-zA-Z0-9_.:/-]*$ (bare names like tier, or collision-resistant namespaced names like https://app.example.com/tier). OIDC Core §5.1.2 recommends collision-resistant or private claim names for non-standard claims; bare names are acceptable when Autentico is used within a private subsystem.

All custom claim endpoints require admin authentication.

Method Endpoint Description
GET /admin/api/users/{id}/claims List a user’s custom claims
POST /admin/api/users/{id}/claims Create or update one custom claim
DELETE /admin/api/users/{id}/claims/{name...} Remove one custom claim
Terminal window
curl https://auth.example.com/admin/api/users/$USER_ID/claims \
-H "Authorization: Bearer $ADMIN_TOKEN"

Response:

{
"data": [
{ "name": "region", "value": "eu", "updated_at": "2026-01-15T10:00:00Z" },
{ "name": "tier", "value": "gold", "updated_at": "2026-01-15T10:00:00Z" }
]
}
Terminal window
curl -X POST https://auth.example.com/admin/api/users/$USER_ID/claims \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "tier", "value": "gold"}'
Field Required Validation
name Yes 1–128 characters, matches the name pattern, not a reserved name
value No Up to 4096 characters

The operation is an upsert: posting an existing name again replaces its value. Returns 201 Created on success, 404 Not Found if the user does not exist, 400 invalid_request for a reserved or malformed name.

Terminal window
curl -X DELETE https://auth.example.com/admin/api/users/$USER_ID/claims/tier \
-H "Authorization: Bearer $ADMIN_TOKEN"

The name is the rest of the path. For a namespaced claim name, percent-encode the slashes as %2F (e.g. .../claims/https:%2F%2Fapp.example.com%2Ftier) — sent raw, the // is path-cleaned and the request is redirected. Returns 404 Not Found if the claim does not exist.

Column Type Description
user_id TEXT Foreign key to users.id
claim_name TEXT The claim name
claim_value TEXT The claim value (string)
created_at DATETIME When the claim was created
updated_at DATETIME Last update timestamp

The primary key is (user_id, claim_name) – one row per pair. Deleting a user cascades to remove all of that user’s custom claims.