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.


auth.conf
# 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

FieldRequiredDescription
methodYesSet to "email" for email/password login
expirationNoLogin token validity duration (e.g., "7d")
successYesSet to true to mark as successful terminal method

OIDC flow fields (method = "oidc")

FieldRequiredDefaultDescription
openIdConfigurationUrlYesURL to the IdP OpenID Connect discovery document
clientIdYesOIDC client ID registered at your IdP
clientSecretYesOIDC client secret
callbackUriYesCallback URL handled by Enterprise Stack after IdP login
accountIdentifierClaimNo"sub"Claim used to uniquely identify the external account
pkceEnabledNotrueEnables PKCE (recommended)
redirectAfterLoginNoDefault URL to redirect after successful login
allowedRedirectUrlsNo[]Allowed patterns for client-specified redirect_to parameter
postLogoutRedirectUriNoDefault URL to redirect after logout
allowedPostLogoutRedirectUrlsNo[]Allowed patterns for client-specified post-logout redirects
externalRoleExtractionNoConfiguration for extracting roles from OIDC tokens
successYesSet 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):

FieldRequiredDefaultDescription
enabledNofalseEnable role extraction from OIDC tokens
realmRolesClaimPathNo"realm_access.roles"JSON path to realm roles in the ID token
clientRolesClaimPathNo"resource_access"JSON path to client roles in the ID token
clientIdNo(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

FieldRequiredDefaultDescription
enabledNofalseTurns external role mapping on/off
strictNotrueIf true, authentication fails when no mapping matches
expectedIssuerNoOnly accept tokens from this issuer
expectedClientIdNoOnly map roles from this client
mappingsNo[]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:

  1. Config file (auth.conf): Define mappings in the externalRoleMapping section. Requires restart to change.
  2. 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:

  1. Database mappings are checked first
  2. If database has any mappings, those are used (config is ignored)
  3. 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" }
]
Last updated on May 7, 2026