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
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
baseUrl | String | Yes | – | Public URL where the issuer service is reachable. |
ciTokenKey | String | No | Generated secp256r1 JWK | Serialized key used to sign/verify OAuth2 access & refresh tokens. |
credentialEncryptionKey | String | No | Not set | Serialized EC P-256 private key used for OID4VCI Credential Request decryption and Credential Response encryption. |
enforcePushedAuthorizationRequests | Boolean | No | false | Require clients to use the Pushed Authorization Request (PAR) endpoint. |
batchCredentialIssuance.batchSize | Integer | No | Not set | Maximum 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-knownmetadata, 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:
baseUrlpoints to your public HTTPS URL.ciTokenKeyis set to a securely generated, persistent key (consider a KMS backend such asaws-rest-api,azure-rest-apiortserather than an inline JWK).- If encrypted Credential Requests or encrypted Credential Responses are required, set
credentialEncryptionKeyto 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
Related Configuration
These concerns are configured in their own files, not in issuer-service.conf:
- Credential Issuer Metadata – OID4VCI metadata configuration (
credential-issuer-metadata.conf) - Issuer Profiles – Credential profiles, including issuer signing keys and DIDs (
issuer2-profiles.conf)
