---
title: "Claim Mappings"
description: "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…"
stack: "Enterprise Stack (commercial) — version 0.21.0"
stack_version: "0.21.0"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/claim-mappings
generated: 2026-07-08
---
# 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:

```json
{
  "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:

```json
{
  "oidcClaim": "given_name",
  "credentialPath": "$.given_name"
}
```

**Example SD-JWT VC payload:**
```json
{
  "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`:

```json
{
  "oidcClaim": "given_name",
  "credentialPath": "$[\"org.iso.18013.5.1\"][\"given_name\"]"
}
```

**Example mDL payload:**
```json
{
  "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`:

```json
{
  "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:

```json
{
  "oidcClaim": "given_name",
  "credentialPath": "$.given_name",
  "transform": "NONE"
}
```

**Example:**
- Input: `"John"`
- Output: `"John"`

### LOWERCASE

Convert to lowercase (useful for email addresses):

```json
{
  "oidcClaim": "email",
  "credentialPath": "$.email",
  "transform": "LOWERCASE"
}
```

**Example:**
- Input: `"JOHN.DOE@EXAMPLE.COM"`
- Output: `"john.doe@example.com"`

### UPPERCASE

Convert to uppercase:

```json
{
  "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:

```json
{
  "oidcClaim": "sub",
  "credentialPath": "$[\"org.iso.18013.5.1\"][\"document_number\"]",
  "transform": "hash_sha256"
}
```

**Example:**
- Input: `"DL123456789"`
- Output: `"5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"`

**info:**

**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", ...}` |

**Note:**

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:

```json
{
  "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:**
```json
{
  "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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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"
        }
      ]
    }
  }
}
```

**info:**

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:

```json
{
  "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.

**warning:**

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:

```json
{
  "oidcClaim": "email",
  "credentialPath": "$.email",
  "transform": "LOWERCASE"
}
```

## Next Steps

- [Multi-Flow Configuration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/multi-flow-config.md) — Configure presentation methods
- [Keycloak Integration](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/iam-integrations/keycloak.md) — See claim mapping in action
- [Setup](https://docs.walt.id/enterprise-stack/services/oidc-bridge-service/setup.md) — Create your OIDC Bridge service
