Issuer Service Configuration

The issuer-service.conf file configures core Issuer2 service settings: the service's public URL, the key used to sign OAuth2 tokens, optional credential encryption support, the Pushed Authorization Request (PAR) policy, and OpenID4VCI Credential Endpoint batch issuance.

File Location

waltid-services/waltid-issuer-api2/config/issuer-service.conf

Configuration Options

PropertyTypeRequiredDefaultDescription
baseUrlStringYes–Public URL where the issuer service is reachable.
ciTokenKeyStringNoGenerated secp256r1 JWKSerialized key used to sign/verify OAuth2 access & refresh tokens.
credentialEncryptionKeyStringNoNot setSerialized EC P-256 private key used for OID4VCI Credential Request decryption and Credential Response encryption.
enforcePushedAuthorizationRequestsBooleanNofalseRequire clients to use the Pushed Authorization Request (PAR) endpoint.
batchCredentialIssuance.batchSizeIntegerNoNot setMaximum number of holder proofs (proofs.jwt) accepted on one Credential Request. Must be 2 or greater when set. Advertised as batch_credential_issuance.batch_size in issuer metadata.

baseUrl

The public URL where the issuer service is accessible. Every OID4VCI endpoint is derived from it:

  • The credential issuer base and metadata URLs become "<baseUrl>/openid4vci". This is what is written into credential offers and the .well-known metadata, so it must be the URL wallets can actually reach.
  • The external OAuth callback becomes "<baseUrl>/openid4vci/external/oauth/callback".

Trailing slashes are trimmed automatically.

baseUrl = "http://localhost:7005"

When running via Docker Compose, environment interpolation is supported:

baseUrl = "http://${SERVICE_HOST}:${ISSUER_API2_PORT}"

ciTokenKey

The key used to sign and verify the OAuth2 access tokens and refresh tokens issued by the service's token endpoint.

The value is a serialized walt.id key — the same format used elsewhere in walt.id — provided as a single JSON string. The type field selects the key backend and is required; omitting it causes startup to fail. Because the value is a string (not a HOCON object), use a triple-quoted HOCON string so the inner quotes do not need escaping:

ciTokenKey = """{"type":"jwk","jwk":{"kty":"EC","d":"...","crv":"P-256","x":"...","y":"..."}}"""

Supported type values include jwk (local key), tse (HashiCorp Vault Transit), aws-rest-api (AWS KMS), azure-rest-api (Azure Key Vault) and oci-rest-api (Oracle Cloud KMS).

Generating a key

The simplest way to get a stable local JWK in exactly the format above is a one-line Node.js command. It is identical on macOS, Linux, and Windows (PowerShell or cmd):

node -e "const{generateKeyPairSync}=require('crypto');const{privateKey}=generateKeyPairSync('ec',{namedCurve:'P-256'});const q=String.fromCharCode(34).repeat(3);console.log('ciTokenKey = '+q+JSON.stringify({type:'jwk',jwk:privateKey.export({format:'jwk'})})+q)"

It prints the complete config line — triple-quoted and ready to paste straight into issuer-service.conf, for example:

ciTokenKey = """{"type":"jwk","jwk":{"kty":"EC","x":"...","y":"...","crv":"P-256","d":"..."}}"""

Generate your own — never reuse the example value from these docs.

If you don't have Node installed, you can instead simply omit ciTokenKey: the service generates a key automatically on startup. Note the limitation described below.

If omitted, a new secp256r1 JWK is generated on every startup. This is fine for local development, but not for production: the signing key changes on each restart, so any tokens issued before a restart can no longer be verified. Always set ciTokenKey explicitly in real deployments.

credentialEncryptionKey

credentialEncryptionKey enables OID4VCI Credential Request encryption and Credential Response encryption.

Issuer2 supports this profile:

  • Key type: EC P-256
  • JWE alg: ECDH-ES
  • JWE enc: A128GCM
  • Compression: not supported

When configured, issuer metadata includes credential_request_encryption and credential_response_encryption with encryption_required = false.

Wallets can keep using normal JSON credential requests. Wallets that send an encrypted Credential Request as application/jwt and include credential_response_encryption receive the Credential Response as an encrypted JWE.

Use a dedicated key for credential encryption. Do not reuse the token signing key or credential signing keys.

credentialEncryptionKey = """{"type":"jwk","jwk":{"kty":"EC","crv":"P-256","x":"...","y":"...","d":"..."}}"""

enforcePushedAuthorizationRequests

Controls the OAuth2 Pushed Authorization Request (PAR, RFC 9126) policy for the authorization code flow. Defaults to false.

When true, clients must submit authorization parameters to the PAR (/par) endpoint instead of passing them directly to /authorize.

Leave this false unless you specifically need to mandate PAR.

enforcePushedAuthorizationRequests = false

batchCredentialIssuance

Optional OpenID4VCI 1.0 Credential Endpoint batch: the wallet sends several holder proofs in proofs.jwt and receives one credential per proof. This is not the draft-13 /batch_credential endpoint, and it is not the same as a multi-credential offer (credentials[] on create-offer).

When set, batchSize must be an integer >= 2. The shipped sample config uses 10. Omit the block to reject proofs with invalid_credential_request. A request whose proof count exceeds batchSize is also rejected with invalid_credential_request.

batchCredentialIssuance {
    batchSize = 10
}

Each proof issues a copy of the selected offer item. If that item includes credentialStatus, Issuer2 embeds the same status entry on every copy. To issue the same profile twice with different status entries, put two items in credentials[] and have the wallet use authorization_details plus credential_identifier.

Example Configuration

# issuer-service.conf

baseUrl = "http://localhost:7005"

# Optional: provide a stable token signing key (recommended for anything beyond local dev)
ciTokenKey = """{"type":"jwk","jwk":{"kty":"EC","d":"...","crv":"P-256","x":"...","y":"..."}}"""

# Optional: enable OID4VCI Credential Request/Response encryption
credentialEncryptionKey = """{"type":"jwk","jwk":{"kty":"EC","crv":"P-256","x":"...","y":"...","d":"..."}}"""

# Optional: require Pushed Authorization Requests
enforcePushedAuthorizationRequests = false

# Optional: allow several holder proofs on one Credential Request
batchCredentialIssuance {
    batchSize = 10
}

Production Configuration

For production deployments, ensure:

  1. baseUrl points to your public HTTPS URL.
  2. ciTokenKey is set to a securely generated, persistent key (consider a KMS backend such as aws-rest-api, azure-rest-api or tse rather than an inline JWK).
  3. If encrypted Credential Requests or encrypted Credential Responses are required, set credentialEncryptionKey to a dedicated EC P-256 private key.
# Production example
baseUrl = "https://issuer.example.com"
ciTokenKey = """{"type":"jwk","jwk":{"kty":"EC","d":"...","crv":"P-256","x":"...","y":"..."}}"""
credentialEncryptionKey = """{"type":"jwk","jwk":{"kty":"EC","crv":"P-256","x":"...","y":"...","d":"..."}}"""
enforcePushedAuthorizationRequests = true

These concerns are configured in their own files, not in issuer-service.conf:

Last updated on September 30, 2026