Claim Mappings
Claim mappings define how attributes from verifiable credentials are extracted and mapped to standard OIDC claims that IAM systems can consume. The OIDC Bridge uses JSONPath expressions to extract values from credentials and optionally transform them before including them in ID tokens.
Understanding Claim Mappings
When a user presents a verifiable credential, the OIDC Bridge:
- Validates the credential signature and structure
- Extracts attribute values using JSONPath expressions
- Transforms values if configured (lowercase, hash, etc.)
- Maps extracted values to OIDC claim names
- Includes the claims in the ID token returned to the IAM system
┌────────────────────┐ JSONPath ┌───────────────────┐
│ Verifiable │ ───────────────────▶ │ OIDC ID Token │
│ Credential │ Extraction + Transform │ │
│ │ │ │
│ { │ │ { │
│ "given_name": │ $.given_name ────▶ │ "given_name": │
│ "John", │ │ "John", │
│ "family_name": │ $.family_name ───▶ │ "family_name": │
│ "DOE", │ + LOWERCASE │ "doe", │
│ "email": │ $.email ─────────▶ │ "email": │
│ "JOHN@X.COM" │ + LOWERCASE │ "john@x.com" │
│ } │ │ } │
└────────────────────┘ └───────────────────┘
Claim Mapping Structure
Each claim mapping consists of three required fields:
{
"oidcClaim": "given_name",
"credentialPath": "$.given_name",
"transform": "NONE"
}
| Field | Description |
|---|---|
| oidcClaim | The target OIDC claim name (e.g., sub, email, given_name) |
| credentialPath | JSONPath expression to extract the value from the credential |
| transform | Optional transformation to apply (default: NONE) |
| credentialId | (Optional) For multi-credential queries, specify which credential to extract from |
Credential Path Expressions
The credentialPath is a JSONPath expression evaluated against the decoded credential's data object — not a wrapper envelope. For an mDL this object is keyed by the ISO namespace, which is why the namespace itself (org.iso.18013.5.1) is the top-level segment of every path. Getting this root wrong is the most common cause of a mapping that silently produces no claim.
The credentialPath format therefore depends on the credential type:
SD-JWT VC (Flat Structure)
SD-JWT VCs use simple JSONPath expressions:
{
"oidcClaim": "given_name",
"credentialPath": "$.given_name"
}
Example SD-JWT VC payload:
{
"given_name": "John",
"family_name": "Doe",
"email": "john.doe@example.com",
"birthdate": "1990-01-15"
}
Common SD-JWT VC paths:
$.given_name→ Given name$.family_name→ Family name$.email→ Email address$.birthdate→ Birth date$.phone_number→ Phone number$.address→ Full address object$.age_over_18→ Boolean age verification
mDL (Namespaced Structure)
mDL credentials use the ISO namespace org.iso.18013.5.1:
{
"oidcClaim": "given_name",
"credentialPath": "$[\"org.iso.18013.5.1\"][\"given_name\"]"
}
Example mDL payload:
{
"org.iso.18013.5.1": {
"given_name": "John",
"family_name": "Doe",
"birth_date": "1990-01-15",
"document_number": "DL123456789"
}
}
Common mDL paths:
$["org.iso.18013.5.1"]["given_name"]→ Given name$["org.iso.18013.5.1"]["family_name"]→ Family name$["org.iso.18013.5.1"]["birth_date"]→ Birth date$["org.iso.18013.5.1"]["document_number"]→ License number$["org.iso.18013.5.1"]["portrait"]→ Photo (JPEG bytes)$["org.iso.18013.5.1"]["age_over_18"]→ Boolean age verification
W3C Verifiable Credentials (JWT)
JWT VCs typically nest attributes under credentialSubject:
{
"oidcClaim": "given_name",
"credentialPath": "$.credentialSubject.given_name"
}
Transform Options
Transforms are applied after extraction but before inclusion in the ID token.
NONE (Default)
Pass the value through unchanged:
{
"oidcClaim": "given_name",
"credentialPath": "$.given_name",
"transform": "NONE"
}
Example:
- Input:
"John" - Output:
"John"
LOWERCASE
Convert to lowercase (useful for email addresses):
{
"oidcClaim": "email",
"credentialPath": "$.email",
"transform": "LOWERCASE"
}
Example:
- Input:
"JOHN.DOE@EXAMPLE.COM" - Output:
"john.doe@example.com"
UPPERCASE
Convert to uppercase:
{
"oidcClaim": "country_code",
"credentialPath": "$.country",
"transform": "UPPERCASE"
}
Example:
- Input:
"us" - Output:
"US"
HASH_SHA256
Hash the value using SHA-256 (useful for pseudonymization). Note the config value is lowercase hash_sha256 — unlike the other transforms, whose config values match their names exactly:
{
"oidcClaim": "sub",
"credentialPath": "$[\"org.iso.18013.5.1\"][\"document_number\"]",
"transform": "hash_sha256"
}
Example:
- Input:
"DL123456789" - Output:
"5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
Privacy Tip: Use hash_sha256 for the sub claim when you don't want the IAM system to store raw credential identifiers.
Standard OIDC Claims
The OIDC specification defines these standard claims:
| Claim | Description | Example |
|---|---|---|
sub | Subject identifier (unique user ID) | "did:key:z6Mk..." or hashed value |
name | Full name | "John Doe" |
given_name | First name | "John" |
family_name | Last name | "Doe" |
middle_name | Middle name | "Robert" |
nickname | Nickname | "Johnny" |
preferred_username | Username | "johndoe" |
email | Email address | "john.doe@example.com" |
email_verified | Email verification status | true |
phone_number | Phone number | "+1-202-555-0123" |
phone_number_verified | Phone verification status | true |
birthdate | Birth date (ISO 8601) | "1990-01-15" |
gender | Gender | "male" |
picture | Profile picture URL | "https://..." |
address | Address object | {"street_address": "123 Main St", ...} |
You can also define custom claims beyond the standard OIDC set. These will be included in the ID token and can be consumed by your IAM system.
Configuration Examples
Example 1: Basic Identity (SD-JWT VC)
Map a simple identity credential to standard OIDC claims:
{
"defaultClaimMappings": [
{
"oidcClaim": "sub",
"credentialPath": "$.email",
"transform": "hash_sha256"
},
{
"oidcClaim": "given_name",
"credentialPath": "$.given_name",
"transform": "NONE"
},
{
"oidcClaim": "family_name",
"credentialPath": "$.family_name",
"transform": "NONE"
},
{
"oidcClaim": "email",
"credentialPath": "$.email",
"transform": "LOWERCASE"
},
{
"oidcClaim": "birthdate",
"credentialPath": "$.birthdate",
"transform": "NONE"
}
]
}
Resulting ID Token:
{
"sub": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
"given_name": "John",
"family_name": "Doe",
"email": "john.doe@example.com",
"birthdate": "1990-01-15",
"iss": "https://enterprise.example.com",
"aud": "my-iam-client",
"exp": 1716048000,
"iat": 1716044400
}
Example 2: mDL Credential
Map a mobile driver's license to OIDC claims:
{
"defaultClaimMappings": [
{
"oidcClaim": "sub",
"credentialPath": "$[\"org.iso.18013.5.1\"][\"document_number\"]",
"transform": "NONE"
},
{
"oidcClaim": "given_name",
"credentialPath": "$[\"org.iso.18013.5.1\"][\"given_name\"]",
"transform": "NONE"
},
{
"oidcClaim": "family_name",
"credentialPath": "$[\"org.iso.18013.5.1\"][\"family_name\"]",
"transform": "NONE"
},
{
"oidcClaim": "birthdate",
"credentialPath": "$[\"org.iso.18013.5.1\"][\"birth_date\"]",
"transform": "NONE"
},
{
"oidcClaim": "picture",
"credentialPath": "$[\"org.iso.18013.5.1\"][\"portrait\"]",
"transform": "NONE"
},
{
"oidcClaim": "age_over_18",
"credentialPath": "$[\"org.iso.18013.5.1\"][\"age_over_18\"]",
"transform": "NONE"
}
]
}
Example 3: Employment Credential
Map an employment credential to custom claims:
{
"defaultClaimMappings": [
{
"oidcClaim": "sub",
"credentialPath": "$.employee_id",
"transform": "NONE"
},
{
"oidcClaim": "email",
"credentialPath": "$.work_email",
"transform": "LOWERCASE"
},
{
"oidcClaim": "department",
"credentialPath": "$.department",
"transform": "NONE"
},
{
"oidcClaim": "job_title",
"credentialPath": "$.job_title",
"transform": "NONE"
},
{
"oidcClaim": "employee_level",
"credentialPath": "$.level",
"transform": "NONE"
}
]
}
Per-Client Claim Mappings
Different OIDC clients can have different claim mapping requirements. Override the default mappings per client:
{
"defaultClaimMappings": [
{
"oidcClaim": "sub",
"credentialPath": "$.email",
"transform": "hash_sha256"
},
{
"oidcClaim": "given_name",
"credentialPath": "$.given_name",
"transform": "NONE"
}
],
"clients": {
"keycloak": {
"clientId": "keycloak",
"clientSecret": "***",
"redirectUris": ["https://keycloak.example.com/callback"],
"claimMappings": [
{
"oidcClaim": "preferred_username",
"credentialPath": "$.email",
"transform": "LOWERCASE"
},
{
"oidcClaim": "given_name",
"credentialPath": "$.given_name",
"transform": "NONE"
},
{
"oidcClaim": "family_name",
"credentialPath": "$.family_name",
"transform": "NONE"
}
]
},
"okta": {
"clientId": "okta",
"clientSecret": "***",
"redirectUris": ["https://okta.example.com/callback"],
"claimMappings": [
{
"oidcClaim": "sub",
"credentialPath": "$.email",
"transform": "NONE"
},
{
"oidcClaim": "email",
"credentialPath": "$.email",
"transform": "LOWERCASE"
}
]
}
}
}
When per-client claimMappings are provided, they replace (not merge with) the defaultClaimMappings for that client.
Multi-Credential Queries
When requesting multiple credentials (OR logic), use the credentialId field to specify which credential each mapping applies to:
{
"defaultClaimMappings": [
{
"credentialId": "option-mdl",
"credentialPath": "$[\"org.iso.18013.5.1\"][\"given_name\"]",
"oidcClaim": "given_name",
"transform": "NONE"
},
{
"credentialId": "option-pid",
"credentialPath": "$.given_name",
"oidcClaim": "given_name",
"transform": "NONE"
},
{
"credentialId": "option-mdl",
"credentialPath": "$[\"org.iso.18013.5.1\"][\"family_name\"]",
"oidcClaim": "family_name",
"transform": "NONE"
},
{
"credentialId": "option-pid",
"credentialPath": "$.family_name",
"oidcClaim": "family_name",
"transform": "NONE"
}
]
}
The OIDC Bridge will use the mappings that match the credential type the user actually presented.
Handling Missing Claims
If a credential doesn't contain a requested attribute (or the JSONPath doesn't resolve), the OIDC Bridge will:
- Skip the claim (it won't appear in the ID token)
- Continue processing other claims
The skip is silent — no warning is logged — so an absent claim in the ID token, rather than an error, is your signal that a credentialPath didn't resolve.
Ensure your DCQL query requests all attributes you're mapping. If you don't request an attribute, the credential may not include it in the response, and the claim mapping will fail.
Best Practices
1. Use Stable Identifiers for sub
The sub claim should be:
- Unique per user
- Stable over time
- Privacy-preserving (consider hashing)
Good choices:
- Hashed document number:
hash_sha256ofdocument_number - DID subject identifier
- Hashed email address
Avoid:
- Raw personally identifiable information
- Values that can change (phone numbers)
2. Normalize Email Addresses
Always apply LOWERCASE transform to email claims:
{
"oidcClaim": "email",
"credentialPath": "$.email",
"transform": "LOWERCASE"
}
Next Steps
- Multi-Flow Configuration — Configure presentation methods
- Keycloak Integration — See claim mapping in action
- Setup — Create your OIDC Bridge service
