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:

  1. Validates the credential signature and structure
  2. Extracts attribute values using JSONPath expressions
  3. Transforms values if configured (lowercase, hash, etc.)
  4. Maps extracted values to OIDC claim names
  5. 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"
}
FieldDescription
oidcClaimThe target OIDC claim name (e.g., sub, email, given_name)
credentialPathJSONPath expression to extract the value from the credential
transformOptional 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:

ClaimDescriptionExample
subSubject identifier (unique user ID)"did:key:z6Mk..." or hashed value
nameFull name"John Doe"
given_nameFirst name"John"
family_nameLast name"Doe"
middle_nameMiddle name"Robert"
nicknameNickname"Johnny"
preferred_usernameUsername"johndoe"
emailEmail address"john.doe@example.com"
email_verifiedEmail verification statustrue
phone_numberPhone number"+1-202-555-0123"
phone_number_verifiedPhone verification statustrue
birthdateBirth date (ISO 8601)"1990-01-15"
genderGender"male"
pictureProfile picture URL"https://..."
addressAddress 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:

  1. Skip the claim (it won't appear in the ID token)
  2. 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_sha256 of document_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

Last updated on July 6, 2026