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 and inherited group claims 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, the effective custom claims assigned directly or inherited through groups are 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"
}

Group inheritance and individual overrides

Section titled “Group inheritance and individual overrides”

Claims defined on groups are inherited as literal group.claim keys. A group app1 with local claim role=user contributes "app1.role": "user". An individual app1.role=editor overrides it; deleting that individual claim reveals the inherited value again. Individual namespaced claims can be assigned without group membership. See User Groups for the group editor and API.

The user claims editor and list endpoint show direct individual assignments. The token and UserInfo flows compute the effective combination. Current UserInfo and newly issued/refreshed tokens reflect changes; existing JWTs retain their original claims. Renaming a group changes its emitted namespace.

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 effective resolver filters reserved top-level names, and the token builder never lets a custom claim overwrite a claim it has already set. Group-local names such as role are allowed because they are emitted as app1.role, not role.

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). Names in group.claim form also accept the group-name alphabet (letters, digits, underscores and hyphens) before the first dot, including a leading digit or hyphen, and may be up to 229 characters. This accommodates a 100-character group name and a 128-character local claim. Other claim names remain limited to 128 characters. 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, or up to 229 in group-prefixed form; matches the name pattern; not a reserved top-level 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.