Auth
This configuration file controls user authentication, password management, token expiration, external identity provider (OIDC) login, and external role-to-enterprise-role mapping.
Example File
signingKey, verificationKey, pepper, and all OIDC settings are example values and must be replaced.
# Will secure login cookies with `Secure` context, enable HTTPS and HTTP->HTTPS redirect
requireHttps = false
# Key (all waltid-crypto supported) to sign login token - has to be key allowing signing (private key)
signingKey = {"type": "jwk", "jwk": {"kty": "OKP", "d": "L_2RuCSFUu818ZzM6Xml6uxesqTcxo8323-Q2S_qq4c", "use": "sig", "crv": "Ed25519", "x": "vvCN3xMAb0ZCt4sWIdtKDhkVHSERJZeBxybN-eSRkgw", "alg": "EdDSA"}}
# Key (all waltid-crypto supported) to verify incoming login tokens - public key is ok.
verificationKey = {"type": "jwk", "jwk": {"kty": "OKP", "d": "L_2RuCSFUu818ZzM6Xml6uxesqTcxo8323-Q2S_qq4c", "use": "sig", "crv": "Ed25519", "x": "vvCN3xMAb0ZCt4sWIdtKDhkVHSERJZeBxybN-eSRkgw", "alg": "EdDSA"}}
# Provide pepper to use for additional password salting (unique string for your deployment,
# has to be shared between instances).
pepper = "waltid-enterprise12345678"
# Hash algorithm to use for passwords for signing.
# You can choose from algorithms like: ARGON2, PBKDF2, PBKDF2_COMPRESSED, BCRYPT, SCRYPT, BALLON_HASHING, MESSAGE_DIGEST, NONE
hashAlgorithm = ARGON2
# If you previously used other (older) password hash algorithms, you
# can use this function to migrate old hashes to new hash algorithms. This
# works at login-time: When a user logs in with a password that uses a hash algorithm
# on this list, the password will be re-hashed in the specified replacement algorithm.
# If null is used as hash algorithm selector, all algorithms expect for the target
# algorithm will be converted automatically.
hashMigrations = {
MESSAGE_DIGEST: ARGON2 # E.g.: Convert all the MD5 hashes to Argon2 hashes
}
authFlows = [
{
method = "email"
expiration = "7d"
success = true
},
{
method = "oidc"
config = {
openIdConfigurationUrl = "http://keycloak:8080/realms/waltid/.well-known/openid-configuration"
clientId = "waltid_enterprise"
clientSecret = "waltid-enterprise-dev-secret"
callbackUri = "http://waltid.enterprise.localhost:3000/auth/account/oidc/callback"
accountIdentifierClaim = "sub"
pkceEnabled = true
# Post-login redirect (optional)
redirectAfterLogin = "http://localhost:5180/"
allowedRedirectUrls = [
"http://localhost:5180/*",
"http://demo.enterprise.localhost:5180/*"
]
# Post-logout redirect (optional)
postLogoutRedirectUri = "http://localhost:5180/"
allowedPostLogoutRedirectUrls = [
"http://localhost:5180/*"
]
# External role extraction from OIDC tokens (optional)
externalRoleExtraction = {
enabled = true
realmRolesClaimPath = "realm_access.roles"
clientRolesClaimPath = "resource_access"
}
}
success = true
}
]
# External role mapping (OPTIONAL - can be managed via API instead)
# externalRoleMapping = {
# enabled = true
# strict = true
# mappings = [
# { externalRole = "tenant-admin", roleId = "waltid.tenant1.BW_ADMIN" }
# ]
# }
Signing & Verification Keys
When users log into the Enterprise Stack, authentication tokens (JWTs) are generated. These tokens are signed with a private key (signingKey) and verified using a public key (verificationKey). Both keys are defined in auth.conf.
Why Signing & Verification Keys?
In multi-instance deployments, tokens issued in one instance must be verifiable in another. To make this work reliably, all instances must use the same signing/verification key pair.
Types of Signing & Verification Keys
signingKey and verificationKey can be provided as a local key (RAW JWK) or a reference to an external KMS-managed key (for example AWS KMS).
Examples
Local
{
"type": "jwk",
"jwk": {
"kty": "OKP",
"d": "L_2RuCSFUu818ZzM6Xml6uxesqTcxo8323-Q2S_qq4c",
"use": "sig",
"crv": "Ed25519",
"x": "vvCN3xMAb0ZCt4sWIdtKDhkVHSERJZeBxybN-eSRkgw",
"alg": "EdDSA"
}
}
AWS
{
"type": "aws",
"config": {
"auth": {
"roleName": "AccessRole",
"region": "eu-central-1"
}
},
"id": "324ebf67-6bcc-4439-8b81-260bf0a82532",
"_publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"RfvJGDXOjz3NOSKgZ0VlijXST8S-j96DrG0C1AMNAK8\",\"y\":\"vSt2q7yIy0-AhAirMuuDmUScxkgf4JVfId-vTETraQA\"}",
"_keyType": "secp256r1"
}
Auth Flows
The Enterprise Stack uses waltid-ktor-authnz for authentication and supports multiple methods in sequence via authFlows.
Why authFlows (plural)
Use authFlows as a list of supported login methods. Typical enterprise setups include both:
email(local account login)oidc(external identity provider login)
Email flow fields
| Field | Required | Description |
|---|---|---|
method | Yes | Set to "email" for email/password login |
expiration | No | Login token validity duration (e.g., "7d") |
success | Yes | Set to true to mark as successful terminal method |
OIDC flow fields (method = "oidc")
| Field | Required | Default | Description |
|---|---|---|---|
openIdConfigurationUrl | Yes | — | URL to the IdP OpenID Connect discovery document |
clientId | Yes | — | OIDC client ID registered at your IdP |
clientSecret | Yes | — | OIDC client secret |
callbackUri | Yes | — | Callback URL handled by Enterprise Stack after IdP login |
accountIdentifierClaim | No | "sub" | Claim used to uniquely identify the external account |
pkceEnabled | No | true | Enables PKCE (recommended) |
redirectAfterLogin | No | — | Default URL to redirect after successful login |
allowedRedirectUrls | No | [] | Allowed patterns for client-specified redirect_to parameter |
postLogoutRedirectUri | No | — | Default URL to redirect after logout |
allowedPostLogoutRedirectUrls | No | [] | Allowed patterns for client-specified post-logout redirects |
externalRoleExtraction | No | — | Configuration for extracting roles from OIDC tokens |
success | Yes | — | Set to true to mark as successful terminal method |
Dynamic Redirect URLs
Clients can specify where to redirect after login by passing a redirect_to query parameter:
GET /auth/account/oidc/auth?redirect_to=https://app.example.com/callback
The URL is validated against allowedRedirectUrls patterns. Wildcards are supported:
"https://app.example.com/*"— allows any path under this origin"https://*.example.com/callback"— allows subdomains
If no redirect_to is specified or validation fails, redirectAfterLogin is used as fallback.
OIDC Logout
The Enterprise Stack supports RP-Initiated Logout. Call the logout endpoint:
GET /auth/account/oidc/logout?post_logout_redirect_uri=https://app.example.com/
This terminates the local session and returns the IdP's end_session_url for full SSO logout.
External Role Extraction (externalRoleExtraction)
Extract roles from OIDC ID tokens (e.g., Keycloak realm/client roles):
| Field | Required | Default | Description |
|---|---|---|---|
enabled | No | false | Enable role extraction from OIDC tokens |
realmRolesClaimPath | No | "realm_access.roles" | JSON path to realm roles in the ID token |
clientRolesClaimPath | No | "resource_access" | JSON path to client roles in the ID token |
clientId | No | (parent clientId) | Client ID for filtering client roles |
The clientId in externalRoleExtraction defaults to the parent config.clientId, so you typically don't need to specify it.
Duration format for expiration
Set expiration using kotlinx.datetime duration syntax (for example 5h, 1d 12h, 1h 0m 30.340s) or simplified ISO-8601 duration (P1DT2H3M4.058S).
Expiration applies to login tokens issued through account login. API key expiration is configured separately in API key settings.
External Role Mapping (externalRoleMapping)
Use externalRoleMapping to map roles from an external IdP (for example Keycloak roles/groups) to Enterprise Stack role IDs.
This section is optional. External role mappings can also be managed via the REST API (database), which is recommended for production deployments as it allows changes without restarting the server.
Fields
| Field | Required | Default | Description |
|---|---|---|---|
enabled | No | false | Turns external role mapping on/off |
strict | No | true | If true, authentication fails when no mapping matches |
expectedIssuer | No | — | Only accept tokens from this issuer |
expectedClientId | No | — | Only map roles from this client |
mappings | No | [] | List of { externalRole, roleId } entries |
Mapping entry format
externalRole: role value received from the external provider.roleId: Enterprise Stack role identifier to assign.
Example
externalRoleMapping = {
enabled = true
strict = true
expectedIssuer = "http://keycloak:8080/realms/waltid"
mappings = [
{ externalRole = "tenant-admin", roleId = "waltid.tenant1.BW_ADMIN" },
{ externalRole = "wallet-operator", roleId = "waltid.tenant1.BW_OPERATOR" }
]
}
This means: if the IdP asserts tenant-admin, the authenticated user gets Enterprise role waltid.tenant1.BW_ADMIN.
Config vs API Mappings
External role mappings can be managed in two ways:
- Config file (
auth.conf): Define mappings in theexternalRoleMappingsection. Requires restart to change. - REST API (recommended): Create mappings via the roles API:
curl -X PUT \ 'https://{host}/v1/{roleId}/roles-api/roles/external-mappings/{externalRole}' \ -H 'Authorization: Bearer {token}' \ -d '{"enabled": true}'
Resolution order:
- Database mappings are checked first
- If database has any mappings, those are used (config is ignored)
- If database has no mappings, config-based mappings are used as fallback
Tenant-aware mapping
In multi-tenant deployments, define tenant-scoped role IDs (waltid.<tenant>.<role>) and map each tenant's external roles explicitly:
mappings = [
# acme tenant
{ externalRole = "acme-tenant-admin", roleId = "waltid.acme.BW_ADMIN" },
{ externalRole = "acme-wallet-operator", roleId = "waltid.acme.BW_OPERATOR" },
# globex tenant
{ externalRole = "globex-tenant-admin", roleId = "waltid.globex.BW_ADMIN" },
{ externalRole = "globex-wallet-operator", roleId = "waltid.globex.BW_OPERATOR" }
]
