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.
Custom claims in tokens
Section titled “Custom claims in tokens”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.
Values are strings
Section titled “Values are strings”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.
Reserved names
Section titled “Reserved names”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.
Naming
Section titled “Naming”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.
Admin API endpoints
Section titled “Admin API endpoints”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 |
List a user’s claims
Section titled “List a user’s claims”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" } ]}Create or update a claim
Section titled “Create or update a claim”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.
Delete a claim
Section titled “Delete a claim”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.
Database table
Section titled “Database table”user_claims table
Section titled “user_claims table”| 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.