Issuer Integration

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

Overview

When client attestation is enabled on an Issuer2 Service, every token request must include valid attestation headers. The issuer verifies:

  1. The attestation JWT signature against a trusted attester key
  2. The PoP JWT signature against the cnf.jwk from the attestation
  3. Various claims (expiration, audience, timestamps, replay protection)

Prerequisites

Before configuring issuer attestation:

  1. Issuer2 Service – An existing Issuer2 Service in your tenant
  2. Attester Public Key – The public key of the Client Attestation Service that will sign attestations

Configuration Options

The clientAttestationConfig object on the Issuer2 Service controls attestation verification:

{
  "clientAttestationConfig": {
    "required": true,
    "verificationMethod": { ... },
    "clockSkewSeconds": 300,
    "replayWindowSeconds": 300
  }
}
ParameterTypeDefaultDescription
requiredBooleantrueWhen true, attestation headers are mandatory. When false, headers are verified if present but not required.
verificationMethodObjectRequiredHow to verify the attestation JWT signature (see below)
clockSkewSecondsLong300Allowed clock skew for time-based validations (exp, iat)
replayWindowSecondsLong300Window for replay protection. PoP JWTs with iat older than this are rejected.

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.

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

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

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

Create an Issuer with Client Attestation

CURL

Endpoint: /v1/{target}/resource-api/services/create | API Reference

Example Request

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": "vc+sd-jwt",
      "vct": "https://example.com/credentials/identity_credential",
      "cryptographic_binding_methods_supported": ["jwk"],
      "credential_signing_alg_values_supported": ["ES256"]
    }
  },
  "clientAttestationConfig": {
    "required": true,
    "verificationMethod": {
      "type": "static-jwk",
      "jwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
        "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
      }
    },
    "clockSkewSeconds": 300,
    "replayWindowSeconds": 300
  }
}'

Body

{
  "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": "vc+sd-jwt",
      "vct": "https://example.com/credentials/identity_credential",
      "cryptographic_binding_methods_supported": ["jwk"],
      "credential_signing_alg_values_supported": ["ES256"]
    }
  },
  "clientAttestationConfig": {
    "required": true,
    "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.

CURL

Endpoint: /v1/{target}/resource-api/configuration | API Reference

Example Request

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 '{
  "clientAttestationConfig": {
    "required": true,
    "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

When client attestation is configured, the issuer's OAuth 2.0 Authorization Server metadata (RFC 8414) will advertise support for attestation-based client authentication:

{
  "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",
    "ES384",
    "ES512",
    "EdDSA"
  ]
}

Wallets can discover this metadata to determine if attestation is required before initiating the credential request flow.

Verification Flow

When a token request arrives, the issuer performs the following verification steps:

  1. Header Presence – Check that both OAuth-Client-Attestation and OAuth-Client-Attestation-PoP headers are present
  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 CodeDescription
invalid_clientMissing headers, invalid signature, claim validation failure, or replay detected
use_fresh_attestationAttestation JWT has expired

Example Error Response

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

Lenient Mode

For development or gradual rollout, you can set required: false to enable lenient mode:

{
  "clientAttestationConfig": {
    "required": false,
    "verificationMethod": { ... }
  }
}

In lenient mode:

  • Requests without attestation headers are accepted
  • Requests with valid attestation headers are accepted
  • Requests with invalid attestation headers are accepted (with a warning logged)

Lenient mode should only be used for development and testing. Production deployments should always use required: true.

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
Last updated on May 8, 2026