---
title: "Issuer Integration"
description: "This guide explains how to configure the Issuer2 Service to require attestation-based client authentication when issuing credentials."
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/client-attestation-service/issuer-integration
generated: 2026-07-20
---
# Issuer Integration

This guide explains how to configure the Issuer2 Service to require attestation-based client authentication when issuing credentials.

## Overview

Issuer2 configures client authentication with `clientAuthenticationConfig`. When a **`client-attestation`** method is present, token requests must include valid `OAuth-Client-Attestation` and `OAuth-Client-Attestation-PoP` headers. The issuer then verifies:

1. The attestation JWT signature against a trusted attester key (or X.509 chain)
2. The PoP JWT signature against the `cnf.jwk` from the attestation
3. Claims such as expiration, audience, timestamps, and replay protection

You can combine **`preauth-anonymous`** with **`client-attestation`**: wallets may then use anonymous pre-authorized code token requests (no `client_id`, no attestation headers) **or** present attestation headers, depending on the flow.

Omit `clientAuthenticationConfig` or use an empty `supportedMethods` list to keep legacy issuer2 behavior where this layer does not enforce client authentication.

## Prerequisites

Before configuring issuer attestation:

1. **Issuer2 Service** – An existing [Issuer2 Service](https://docs.walt.id/enterprise-stack/services/issuer2-service/setup.md) in your tenant
2. **Attester trust material** – Public JWK, KMS key reference, or X.509 trust anchors matching how the Client Attestation Service signs attestations

## Configuration Options

Use the **`clientAuthenticationConfig`** object on the Issuer2 Service. It contains a **`supportedMethods`** array. Each element is a tagged object (`type` discriminator):

| `type` | Purpose |
|--------|---------|
| `preauth-anonymous` | Allows OID4VCI **pre-authorized code** token requests **without** `client_id` when no attestation headers are sent. At most one entry. |
| `client-attestation` | Requires valid attestation headers on the token request whenever that path is used. Includes nested **`config`** (see below). At most one entry. |

Example — **attestation only** (strict wallet authentication):

```json
{
  "clientAuthenticationConfig": {
    "supportedMethods": [
      {
        "type": "client-attestation",
        "config": {
          "verificationMethod": { "...": "see below" },
          "clockSkewSeconds": 300,
          "replayWindowSeconds": 300
        }
      }
    ]
  }
}
```

Example — **anonymous pre-authorized code only** (no attestation):

```json
{
  "clientAuthenticationConfig": {
    "supportedMethods": [
      { "type": "preauth-anonymous" }
    ]
  }
}
```

Example — **both** (wallets can use anonymous pre-auth for pre-authorized offers without `client_id`, or send attestation when required):

```json
{
  "clientAuthenticationConfig": {
    "supportedMethods": [
      { "type": "preauth-anonymous" },
      {
        "type": "client-attestation",
        "config": {
          "verificationMethod": { "...": "see below" }
        }
      }
    ]
  }
}
```

### `client-attestation` → `config` parameters

These fields apply to the nested object under `client-attestation`:

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `verificationMethod` | Object | Required | How to verify the attestation JWT signature (see below) |
| `clockSkewSeconds` | Long | `300` | Allowed clock skew for time-based validations (exp, iat) |
| `replayWindowSeconds` | Long | `300` | Replay window for PoP `iat` |

## Verification Methods

### Static JWK

Verify the attestation JWT using a static public key provided inline. This is the simplest method, suitable for testing or when the attester key is known and stable.

```json
{
  "verificationMethod": {
    "type": "static-jwk",
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
      "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
    }
  }
}
```

### KMS Key

Verify against a key stored in the KMS. This is useful when the attester and issuer share the same enterprise instance.

```json
{
  "verificationMethod": {
    "type": "kms-key",
    "keyReference": "waltid.tenant1.kms1.attester-signing-key"
  }
}
```

### X.509 Chain

Verify via X.509 certificate chain validation. The attestation JWT must carry an `x5c` header with the certificate chain, which is validated against configured trust anchors.

This method is required for regulated environments (e.g., VICAL, eIDAS) where attesters must be certified.

```json
{
  "verificationMethod": {
    "type": "x509-chain",
    "trustedRootCertificatesPem": [
      "-----BEGIN CERTIFICATE-----
MIIB...
-----END CERTIFICATE-----"
    ]
  }
}
```

## Create an Issuer with Client Attestation

**Option: CURL**

Endpoint: `/v1/{target}/resource-api/services/create` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/post_v1__target__resource_api_services_create)

**Example Request**

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/services/create' \
  -H 'accept: */*' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "type": "issuer2",
  "baseUrl": "https://myorg.enterprise-sandbox.waltid.dev",
  "kms": "waltid.tenant1.kms1",
  "tokenKeyId": "waltid.tenant1.kms1.tokenKey",
  "supportedCredentialTypes": {
    "identity_credential_vc+sd-jwt": {
      "format": "dc+sd-jwt",
      "vct": "https://example.com/credentials/identity_credential",
      "cryptographic_binding_methods_supported": ["jwk"],
      "credential_signing_alg_values_supported": ["ES256"]
    }
  },
  "clientAuthenticationConfig": {
    "supportedMethods": [
      {
        "type": "client-attestation",
        "config": {
          "verificationMethod": {
            "type": "static-jwk",
            "jwk": {
              "kty": "EC",
              "crv": "P-256",
              "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
              "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
            }
          },
          "clockSkewSeconds": 300,
          "replayWindowSeconds": 300
        }
      }
    ]
  }
}'
```

**Body**

```json
{
  "type": "issuer2",
  "baseUrl": "https://myorg.enterprise-sandbox.waltid.dev",
  "kms": "waltid.tenant1.kms1",
  "tokenKeyId": "waltid.tenant1.kms1.tokenKey",
  "supportedCredentialTypes": {
    "identity_credential_vc+sd-jwt": {
      "format": "dc+sd-jwt",
      "vct": "https://example.com/credentials/identity_credential",
      "cryptographic_binding_methods_supported": ["jwk"],
      "credential_signing_alg_values_supported": ["ES256"]
    }
  },
  "clientAuthenticationConfig": {
    "supportedMethods": [
      {
        "type": "client-attestation",
        "config": {
          "verificationMethod": {
            "type": "static-jwk",
            "jwk": {
              "kty": "EC",
              "crv": "P-256",
              "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
              "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
            }
          },
          "clockSkewSeconds": 300,
          "replayWindowSeconds": 300
        }
      }
    ]
  }
}
```

## Update an Existing Issuer

To add or modify client attestation configuration on an existing Issuer2 Service, use the configuration update endpoint.

**Option: CURL**

Endpoint: `/v1/{target}/resource-api/configuration` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20Resource/put_v1__target__resource_api_configuration)

**Example Request**

```bash
curl -X 'PUT' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/resource-api/configuration' \
  -H 'accept: */*' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "clientAuthenticationConfig": {
    "supportedMethods": [
      {
        "type": "client-attestation",
        "config": {
          "verificationMethod": {
            "type": "kms-key",
            "keyReference": "waltid.tenant1.kms1.attester-signing-key"
          }
        }
      }
    ]
  }
}'
```

**Path Parameters**

- `orgID` – Your organization's Base URL
- `target` – The issuer2 service target (`{organizationID}.{tenantID}.{issuer2ServiceID}`), e.g. `waltid.tenant1.issuer1`

## Authorization Server Metadata

The issuer’s OAuth 2.0 Authorization Server metadata (RFC 8414) reflects `clientAuthenticationConfig`:

- **`token_endpoint_auth_methods_supported`** includes `attest_jwt_client_auth` only when a `client-attestation` method is configured.
- When attestation is advertised, **`client_attestation_signing_alg_values_supported`** and **`client_attestation_pop_signing_alg_values_supported`** are currently emitted as **`["ES256"]`** each (what wallets should use for those JWTs in this profile).
- **`pre_authorized_grant_anonymous_access_supported`** is **`true`** only when a **`preauth-anonymous`** method is included. It is **`false`** when attestation alone is configured.

Example - **attestation only**:

```json
{
  "issuer": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci",
  "token_endpoint": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci/token",
  "token_endpoint_auth_methods_supported": ["attest_jwt_client_auth"],
  "client_attestation_signing_alg_values_supported": ["ES256"],
  "client_attestation_pop_signing_alg_values_supported": ["ES256"],
  "pre_authorized_grant_anonymous_access_supported": false
}
```

Example - **`preauth-anonymous` only** (no attestation in metadata):

```json
{
  "issuer": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci",
  "token_endpoint": "https://myorg.enterprise-sandbox.waltid.dev/v2/waltid.tenant1.issuer1/issuer-service-api/openid4vci/token",
  "pre_authorized_grant_anonymous_access_supported": true
}
```

Wallets should read this metadata before the token request to decide whether to attach attestation headers, send `client_id`, or rely on anonymous pre-authorized code.

## Verification Flow

When attestation headers are present (or required because `client-attestation` is the only usable method for the request), the issuer performs the following verification steps:

1. **Routing** – If either attestation header is present, the issuer validates both headers together. If attestation is configured but neither header is present, the request is rejected unless the **`preauth-anonymous`** path applies (pre-authorized code grant **without** `client_id`). Sending only one of the two headers fails validation.
2. **Attestation JWT Signature** – Verify the attestation JWT signature against the configured verification method
3. **Attestation JWT Type** – Verify `typ` is `oauth-client-attestation+jwt`
4. **Subject Claim** – Verify `sub` is present and matches `client_id` if provided
5. **Expiration** – Verify `exp` is not in the past (with clock skew tolerance)
6. **Confirmation Key** – Extract `cnf.jwk` (must not be a private key)
7. **PoP JWT Signature** – Verify the PoP JWT signature against `cnf.jwk`
8. **PoP JWT Type** – Verify `typ` is `oauth-client-attestation-pop+jwt`
9. **Audience** – Verify `aud` matches the issuer's RFC 8414 issuer identifier URL
10. **Issuance Time** – Verify `iat` is within the replay window
11. **Replay Protection** – Verify `jti` has not been used before

## Error Responses

When attestation verification fails, the issuer returns an OAuth 2.0 error response:

| Error Code | Description |
|------------|-------------|
| `invalid_client` | Token request does not match configured client authentication (for example missing attestation headers when `client-attestation` is required, only one of the two attestation headers present, invalid signatures, claim validation failure, replay detected, or attestation headers sent when this issuer does not accept them) |
| `use_fresh_attestation` | Attestation JWT has expired |

**Example Error Response**

```json
{
  "error": "invalid_client",
  "error_description": "Invalid client attestation JWT signature"
}
```

## Best Practices

1. **Use KMS Key or X.509 Chain in Production** – Static JWK is convenient for testing but harder to rotate
2. **Set Appropriate Validity Periods** – Balance security (shorter) with usability (longer)
3. **Monitor for Replay Attacks** – The issuer logs warnings when replay is detected
4. **Rotate Attester Keys Periodically** – Update the verification method when rotating keys
5. **Use X.509 Chain for Regulated Environments** – Required for VICAL, eIDAS, and similar frameworks
