Configuration Guide

March 2, 2026 · View on GitHub

Using an Access and Identity Management Broker

Adding VC Authn as Identity Provider

The first step is to add VC Authn as a new Identity Provider for our AIM system. The following instructions are built for Keycloak, but should be applicable for any AIM that supports Open ID Connect.

  1. Click on the Identity Providers tab and select Open ID Connect v1.0 from the User-defined section. vc-authn-oidc-flow

  2. In the next page, select an alias and a display name for your Idp. The alias will be used to generate a unique URL corresponding to the new provider, while the display name will be used in the Keycloak login screen on the button corresponding to the IdP. vc-authn-oidc-flow

  3. We will now configure the Open Id Connect parameters for our new provider.

[!NOTE] ACAPy VC-AuthN exposes the .well-known/openid-configuration endpoint to provide systems that support it automatic discovery of the endpoints and features of the Identity Provider. If you decide to do so, switch on Use discovery endpoint and enter {VC_AUTHN_PUBLIC_URL}/.well-known/openid-configuration to proceed.

To input settings manually, or review them:

  • Authorization URL: this must be set to {PUBLIC_VC_AUTHN_URL}/authorize

  • Token URL: this must be set to {PUBLIC_VC_AUTHN_URL}/token

  • User Info URL: this must be set to {PUBLIC_VC_AUTHN_URL}/userinfo. VC Authn supports this endpoint via ephemeral session caching. It is required for RPs like Firebase, but optional for others that rely solely on ID Tokens.

  • Client ID/Client Secret: these settings will be used to identify and secure the IdP integration between Keycloak and VC Authn. Make sure the client secret parameter is unique to your VC Authn instance. ACAPy VC-AuthN supports two methods of client authentication: Client secret sent as basic auth and Client secret sent as post.

  • Default Scopes: this must be set to vc_authn to instruct the AIM broker which scopes to request from the IdP.

  • Validate Signatures: if you want to have the signature of ACAPy VC-AuthN validated by Keycloak, turn this on, flip the Use JWKS URL to true and set JWKS URL to {PUBLIC_VC_AUTHN_URL}/.well-known/openid-configuration/jwks.

  • Forwarded Query Parameters: set this to pres_req_conf_id. This parameter is used by VC Authn to lookup in its database the configuration to generate presentation request to be displayed to the user and the AIM system needs to forward it when initiating the authentication.

vc-authn-oidc-flow

Save the settings and take note of the generated Redirect URI and Client ID/Secret parameters, they will be used in the next steps.

Configuring VC Authn

ACAPy VC-AuthN can be configured by using the API endpoints exposed on Swagger at VC_AUTHN_PUBLIC_URL}/docs. The oidc_clients namespace provides RESTful APIs to create/delete/update clients.

To register a new client, POST a request to the /clients endpoint with a payload containing the client id/secret and redirect URL noted at the previous step. Example:

{
  "client_id": "my-new-client",
  "client_name": "my-keycloak",
  "client_secret": "super-secret",
  "response_types": ["code", "id_token", "token"],
  "token_endpoint_auth_method": "client_secret_post",
  "redirect_uris": [
    "http://localhost:8880/auth/realms/vc-authn/broker/vc-authn/endpoint"
  ]
}

Mappers

Once the new Identity Provider is configured, mappers should be added in order to consume the claims in issued tokens. This can be done by clicking Add mapper in the Mappers tab within the Identity Provider configuration view. Mappers should be configured using the type Attribute Importer and, at minimum, should include:

  • pres_req_conf_id: this will return the id of the proof request configuration that was used during the authentication request. It should be used by the client application to check authentication was completed by using the expected credential (see best practices for additional information).
  • vc_presented_attributes: this will contain a serialized JSON object containing all of the attributes requested as part of the proof request, for the application to consume. If individual mappers are preferred, they can be configured to extract individual claims.

The following is an example mapper configuration: vc-authn-oidc-flow

Direct Configuration

ACAPy VC-AuthN 2.0 only supports confidential clients, and cannot be configured to be invoked directly from Single-Page applications. For back-end systems, however, the above instructions should still apply.

Environment Variables

Several functions in ACAPy VC-AuthN can be tweaked by using the following environment variables.

VariableTypeWhat it doesNOTES
ACAPY_PROOF_FORMATstring ("indy" or "anoncreds")Sets the proof format for ACA-Py presentation requests. Use anoncreds for modern AnonCreds support or indy for legacy Indy-based ecosystems.Defaults to indy for backward compatibility with existing deployments.
AGENT_TENANT_MODEstring ("single", "multi", "traction")single: Standard single-tenant agent.
multi: Standard ACA-Py Multi-tenancy (authenticates via Admin API using Wallet ID/Key).
traction: Traction Multi-tenancy (authenticates via Tenant API using Tenant ID/API Key).
ACAPY_TENANT_WALLET_IDstringIf Mode=multi: The wallet_id of the sub-wallet.
If Mode=traction: The tenant_id provided by Traction.
ACAPY_TENANT_WALLET_KEYstringIf Mode=multi: The wallet_key used to unlock the sub-wallet.
If Mode=traction: The Tenant api_key provided by Traction.
ACAPY_TOKEN_CACHE_TTLintThe duration (in seconds) to cache authentication tokens in memory for multi/traction modes. Defaults to 3600 (1 hour).Reduce this if your ACA-Py/Traction instance issues tokens with short expiration times.
CONTROLLER_ENABLE_USERINFO_ENDPOINTboolFeature Flag. Enables the OIDC /userinfo endpoint. Defaults to false. Set to true to enable compatibility with Firebase and other RPs that require UserInfo.Opt-in. This endpoint relies on ephemeral session storage.
OIDC_ACCESS_TOKEN_TTLintThe duration (in seconds) for which OIDC Access Tokens are valid. Defaults to 3600 (1 hour). In Redis mode, this also controls how long user claims are cached to ensure the userinfo endpoint works for the life of the token.
CONTROLLER_WEB_HOOK_URLstring (URL)Required for Multi-Tenant Mode. The public or internal URL where the ACA-Py Agent can reach this Controller to send webhooks (e.g. http://controller:5000/webhooks).The controller will automatically register this URL with the specific tenant wallet on startup.
SET_NON_REVOKEDboolif True, the non_revoked attributed will be added to each of the present-proof request requested_attribute and requested_predicate with 'from=0' and'to=int(time.time())
USE_OOB_LOCAL_DID_SERVICEboolInstructs ACAPy VC-AuthN to use a local DID, it must be used when the agent service is not registered on the ledger with a public DIDUse this when ACAPY_WALLET_LOCAL_DID is set to true in the agent.
WALLET_DEEP_LINK_PREFIXstringCustom URI scheme and host to use for deep links (e.g. {WALLET_DEEP_LINK_PREFIX}?c_i={connection invitation payload)Default bcwallet://aries_proof-request
LOG_WITH_JSONboolIf True, logging output should printed as JSON if False it will be pretty printed.Default behavior will print as JSON.
LOG_TIMESTAMP_FORMATstringdetermines the timestamp formatting used in logsDefault is "iso"
LOG_LEVEL"DEBUG", "INFO", "WARNING", or "ERROR"sets the minimum log level that will be printed to standard outDefaults to DEBUG
SIEM_AUDIT_ENABLEDboolEnables or disables SIEM audit event logging. When false, all audit events (session lifecycle, proof verification, token issuance, etc.) are silently suppressed.Defaults to true (audit logging is on).
SIEM_IP_SALTstringSalt used for one-way hashing of client IP addresses in SIEM audit logs. Should be rotated periodically. Only relevant when SIEM_AUDIT_ENABLED is true.Defaults to a built-in placeholder. Set a unique value in production.

SIEM Audit Logging

ACAPy VC-AuthN includes a privacy-preserving audit logger for SIEM (Security Information and Event Management) platforms. It emits structured events at key points in the verification flow—session initiation, QR scan, proof verification, token issuance, session expiry, etc.—without ever logging PII or credential attribute values.

Audit Events

The following events are emitted by the SIEM audit logger:

EventCategoryDescriptionKey Fields
auth_session_initiatedSession lifecycleAn OIDC authorization request created a new auth session.session_id, client_id, ver_config_id, client_ip_hash, user_agent_family, requested_schemas, requested_attributes_count, requested_predicates_count
proof_request_createdProof request flowA proof request was built and is ready for scanning.session_id, ver_config_id, proof_name, requested_schemas, expected_issuers
qr_scannedProof request flowThe QR code was scanned or the deep link was invoked by a wallet.session_id, scan_method (qr_code / deep_link), client_ip_hash, user_agent_family
proof_verifiedProof request flowProof presentation was successfully verified.session_id, ver_config_id, outcome=verified, credential_schemas, issuer_dids, credential_count, revocation_checked, duration_ms
proof_verification_failedProof request flowProof verification failed.session_id, ver_config_id, outcome=failed, failure_category, duration_ms
session_abandonedSession terminationThe user abandoned or declined the proof request.session_id, ver_config_id, outcome=abandoned, phase, duration_ms
session_expiredSession terminationThe session expired before the proof flow completed.session_id, ver_config_id, outcome=expired, phase, timeout_seconds
token_issuedToken flowAn OIDC ID token was successfully issued to the relying party.session_id, client_id, ver_config_id, claims_count, duration_ms
webhook_receivedSecurity / monitoringAn ACA-Py agent webhook was received.webhook_topic, webhook_state, webhook_role
invalid_client_requestSecurity / monitoringA client request failed validation (unknown client, bad params).client_id, error_type, client_ip_hash

All events share a common base structure containing audit_event_type, timestamp, and service. Fields that are None are omitted from the log output for cleaner JSON.

Privacy note: No attribute values, subject identifiers, or credential contents are ever included. Only safe metadata (schema names, issuer DIDs, counts, durations, and anonymized IPs) is logged.

Audit logging is enabled by default. There are two complementary ways to control it:

1. Feature flag (SIEM_AUDIT_ENABLED)

Set the environment variable to false to disable all SIEM audit events at the application level. No audit log lines will be emitted regardless of log-level settings.

environment:
  - SIEM_AUDIT_ENABLED=false        # disable SIEM audit events entirely

To re-enable, remove the variable or set it to true (the default).

2. Log-level override (logconf.json / LOG_LEVEL)

The siem.audit logger is configured in logconf.json at the INFO level. You can raise it to WARNING or CRITICAL to suppress the INFO-level audit messages while keeping the feature flag on—useful if you want to silence routine events but still capture any future warning/error-level audit entries.

{
  "loggers": {
    "siem.audit": {
      "level": "WARNING",
      "handlers": ["out"],
      "propagate": false
    }
  }
}

Alternatively, setting the global LOG_LEVEL environment variable to WARNING or higher will also suppress the audit messages, but this will affect all loggers, not just SIEM.

IP anonymization salt

When audit logging is enabled, client IP addresses are one-way hashed before being logged. The hash uses a salt read from the SIEM_IP_SALT environment variable. The default salt is a placeholder—you should set a unique, secret value in production and rotate it periodically.

environment:
  - SIEM_IP_SALT=my-unique-production-salt

Legacy Variable Support

For backward compatibility with version 2.x deployments, the legacy MT_ variables are still supported in multi mode. However, ACAPY_TENANT_ variables take precedence if both are set.

Legacy VariableMapped toStatus
MT_ACAPY_WALLET_IDACAPY_TENANT_WALLET_IDDeprecated
MT_ACAPY_WALLET_KEYACAPY_TENANT_WALLET_KEYDeprecated

Deployment Examples

Standard Multi-Tenant (ACA-Py):

environment:
  - AGENT_TENANT_MODE=multi
  - ACAPY_TENANT_WALLET_ID=3ffd2cf2-5fee-434a-859b-c84f1677e647
  - ACAPY_TENANT_WALLET_KEY=my-secure-wallet-key
  - CONTROLLER_WEB_HOOK_URL=http://controller:5000/webhooks

Traction Tenancy Mode

The traction mode (AGENT_TENANT_MODE="traction") is designed specifically for environments where the ACA-Py Admin API endpoints (e.g., /multitenancy/wallet/...) are restricted or blocked for security reasons, which is true of many traction environments.

In this mode:

  1. Authentication: The controller authenticates directly as the tenant using ACAPY_TENANT_WALLET_ID (as the Tenant ID) and ACAPY_TENANT_WALLET_KEY (as the Tenant API Key) to obtain a Bearer token. Both values must be provided; there is no fallback to legacy keys.
  2. Webhook Registration: The controller bypasses the Admin API and uses the authenticated Tenant API (PUT /tenant/wallet) to register the CONTROLLER_WEB_HOOK_URL.

Example Docker Configuration for Traction:

environment:
  - AGENT_TENANT_MODE=traction
  - ACAPY_TENANT_WALLET_ID=00000000-0000-0000-0000-000000000000
  - ACAPY_TENANT_WALLET_KEY=your-traction-api-key
  - CONTROLLER_WEB_HOOK_URL=https://your-controller-url.com/webhooks
  - ACAPY_ADMIN_URL=https://traction-tenant-proxy-url

Multi-Tenant and Traction Webhook Registration

When running in Multi-Tenant (AGENT_TENANT_MODE="multi") or Traction (AGENT_TENANT_MODE="traction") modes, the ACA-Py Agent may not automatically know where to send verification success notifications for specific tenant wallets.

To address this, the VC-AuthN Controller performs an automatic handshake on startup:

  1. It checks if CONTROLLER_WEB_HOOK_URL is defined.
  2. If the Controller is protected by CONTROLLER_API_KEY, it appends the key to the URL using the #<key> syntax. This instructs ACA-Py to parse the hash and send it as an x-api-key header in the webhook request.
    • Important: The full webhook URL with the fragment (#<CONTROLLER_API_KEY>) is sensitive and should be treated as secret data. Do not log, expose, or share the full URL with the fragment, as it can be used to access protected endpoints. Always mask or redact the API key in logs and documentation.
  3. It attempts to connect to the ACA-Py Agent to update the specific Tenant Wallet configuration.
    • Multi Mode: Uses the Admin API (/multitenancy/wallet/{id}) authenticated with ST_ACAPY_ADMIN_API_KEY.
    • Traction Mode: Uses the Tenant API (/tenant/wallet) authenticated with the Tenant Token acquired via ACAPY_TENANT_WALLET_KEY.
  4. If the Agent is not yet ready, the Controller will retry the registration up to 5 times before logging an error.

This ensures that even in complex Docker/Kubernetes network topologies, the Agent sends verification signals to the correct Controller instance.

Proof Request Configuration Options

The basic structure of a proof-request configuration is described here. Additional options are described via the Swagger document, and listed below:

  • include_v1_attributes: defaults to false, switch to true if root-level claims as presented in ACAPy VC-AuthN v1 are still required for the proof-request.

Proof Substitution Variables

Proof Request configurations (as described here) are pre-set records stored in the ACAPy VC-AuthN database comprising of the details to make the proof request from. At runtime when the user is directed to the proof challenge, the appropriate configuration is fetched and the proof request built from that.
In certain use cases you may want the proof request to have a specific value in the proof (probably in a requested predicate) generated at that moment rather than preset.

An example could be using today's date, a "right now" timestamp, or an age-check (IE today's date minus 19 years).
To accomodate this, ACAPy VC-AuthN proof substitution variables can be used as placeholders in the configurations. They can just be added as string in the configuration with a $ prefix. There are a handful of built-in options:

Substitution VariableDetails
$nowInserts the current timestamp in seconds since the epoch as an int
$today_intInserts Today's date in YYYYMMDD format as an int
$tomorrow_intInserts Tomorrow's date in YYYYMMDD format as an int
$threshold_years_XSupply a number for X. Inserts today's date minus X years in YYYYMMDD format as an int

$threshold_years_X is an example of a 'dynamic' substitution variable where part of the variable name can act as a parameter

So in a proof request config a requested predicate could have

          "p_value": "$today_int",
          "p_type": ">"

and at runtime when the user navigates to the QR code page, the proof would include something like

          "p_value": 20240927,
          "p_type": ">"

See the oidc-controller\api\verificationConfigs\variableSubstitutions.py file for implementations.

Customizing variables

For user defined variable substitutions users can set the environment variable to point at a python file defining new substitutions.

User Defined Variable API

In oidc-controller\api\verificationConfigs\variableSubstitutions.py you will find the method add_variable_substitution which can be used to modify the existing instance of VariableSubstitutionMap named variable_substitution_map.

Takes a valid regular expression pattern and a function who's arguments correspond with each regex group substitution_function. Each captured regex group will be passed to the function as a str.

Here is an example python file that would define a new variable substitution $today_plus_x_times_y which will add X days multiplied by Y days to today's date

from datetime import datetime, timedelta

def today_plus_times(added_days: str, multiplied_days: str) -> int:
	return int(
		((datetime.today() + timedelta(days=int(added_days))) * timedelta(days=int(multiplied_days)))
	).strftime("%Y%m%d"))

# variable_substitution_map will already be defined in variableSubstitutions.py
variable_substitution_map.add_variable_substitution(r"\$today_plus_(\d+)_times_(\d+)", today_plus_times)

For an example of this python file see docker/oidc-controller/config/user_variable_substitution_example.py

All that is necessary is the adding of substitution variables.These changes will be applied by ACAPy VC-AuthN during startup. The variable variable_substitution_map will already be defined.

After loading the python file during the service startup each new user defined variable is logged for confirmation. Any failures to load these changes will be logged. If no new definitions are found indication of this will also be logged

User Customized QR Page

ConfigMap Based Overrides

By default the QR page shown to users uses OpenWallet branding users are capable of overriding any of these files using a custom ConfigMap.

A custom configmap overriding any of the HTML files in html-templates can be used to customize the UI. The keys to be used to override each html component are:

One example of the new ConfigMap would be

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: user-template-overrides
data:
  verified_credentials.html: "<p>New contents of the verified_credentials.html page</p>"
  wallet_howto.html: "<p>New contents of the wallet_howto.html page</p>"
  ver_config_explorer.html: "<p>New contents of the ver_config_explorer.html page</p>"

The name of this new/existing ConfigMap needs to be added to controller.customHtmlConfigMapName value in values.yaml for it to be mounted.

These files will override the existing files seen in html-templates.

Complete Directory Overrides

In addition, to allow for more flexibility users are welcome to build an image based on ghcr.io/openwallet-foundation/acapy-vc-authn-oidc mounting their complete directory with new assets.

To inform the oidc controller of this new directory update .Values.controller.templateDirectory to the location of this new directory

HTML Template Formats

The HTML templates use Jinja2 to insert the necessary information from the oidc controller.

The provided template sections are

  • image_contents: a base64 encoded image used as the QR code
  • url_to_message: URL the QR code points to
  • callback_url: URL used when verification is complete
  • pres_exch_id: id of the presentation exchange for this authsession
  • pid: auth_session id
  • controller_host: URL pointing to this controller
  • challenge_poll_uri: URL used for polling the authsessions state
  • wallet_deep_link: deep link used to integrate with bcwallet for mobile users
  • title: Title of the current proof request
  • claims: List of claims requested in this proof request

For more information please see