Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ USER_PASS_PID_DEMO_PASSWORD=vErY$3cure
PRE_AUTHORIZED_CREDENTIAL_ISSUANCE=true
PRE_AUTHORIZED_CODE_API_URL=http://localhost:8003/api
PRE_AUTHORIZED_CODE_API_BEARER_TOKEN=secret-bearer-token
OPENID4VC_PROFILE=DEFAULT
ABCA_CLIENT_ATTESTATION_MAX_AGE=86400
ABCA_CLIENT_ATTESTATION_POP_MAX_AGE=60
ABCA_CHALLENGE_SECRET=replace-with-a-64-len-secret
DPOP_NONCE_SECRET=replace-with-a-32+-len-secret
WEBUILD_ABCA_STATUS_LIST_ALLOWED_ORIGINS=http://localhost:8002
57 changes: 36 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,42 @@ Configuration is loaded from `.env` (see `.env.template`). Values are read via `

Environment variables and how they are used:

| Variable | Purpose | Default / Notes |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `SERVICE_URL` | Full external URL for the AS (scheme/host and optional path prefix). | `http://localhost:6060` if unset. Example: If deployed under `/as`, set it to `https://issuer.example.com/as`. |
| `WALLET_URL` | Redirect URI registered for the wallet client. | `http://localhost:3000` if unset. |
| `OIDC_JWKS_PATH` | Optional override for the OIDC provider signing JWKS path used for ID tokens and JWT authorization responses. | Defaults to `./keys/oidc.jwks.json` |
| `INTROSPECTION_CLIENT` | Client ID allowed to introspect tokens. | If set with `INTROSPECTION_CLIENT_SECRET`, an extra client is registered. |
| `INTROSPECTION_CLIENT_SECRET` | Secret for the introspection client. | Required alongside `INTROSPECTION_CLIENT`. |
| `SCOPES` | Comma-separated list of supported scopes. | Split by `,` and passed to OIDC provider `scopes`. |
| `METADATA_URL` | Credential Issuer metadata URL. | Used during consent to fetch display metadata for requested scopes. |
| `ACCESS_TOKEN_TTL` | Access token TTL (seconds). | Default: `30` |
| `REFRESH_TOKEN_TTL` | Refresh token TTL (seconds). | Default: `2592000` |
| `BASE_PATH` | Optional base path for public, AS/OIDC, and related Express endpoints. | Empty by default. Supports values like `as` or `/as`; normalized to `/as`. |
| `AUTHENTICATOR` | Selected authenticator to load (single value). | Supported values: `user-pass-pid` or `auth-broker`. Defaults to `user-pass-pid` if unset. |
| `USER_PASS_PID_DEMO_USERNAME` | Demo username for the `user-pass-pid` login screen and demo account. | Used only when `AUTHENTICATOR=user-pass-pid`. |
| `USER_PASS_PID_DEMO_PASSWORD` | Demo password shown in `user-pass-pid` login form. | Used only for prefill; authentication does not check password. |
| `AUTH_BROKER_PROVIDER_URL` | External OIDC provider URL for the `auth-broker` authenticator. | Required when `auth-broker` is enabled. |
| `AUTH_BROKER_CLIENT_ID` | OIDC client ID for the `auth-broker` authenticator. | Required when `auth-broker` is enabled. |
| `AUTH_BROKER_CLIENT_SECRET` | OIDC client secret for the `auth-broker` authenticator. | Optional for public clients. |
| `AUTH_BROKER_SCOPE` | Space-separated scopes sent to external IdP authorize endpoint by `auth-broker`. | Default: `openid profile email`. |
| `AUTH_BROKER_REDIRECT_URI` | Redirect URI handled by wallet-as auth-broker callback route. | Default: `SERVICE_URL + /interaction/authBroker/callback`. |
| `AUTH_BROKER_SKIP_LOGOUT` | Skip external IdP logout even if IdP supports it in metadata in `auth-broker` callback flow. | Optional; set to `true` when logout should be skipped. |
| Variable | Purpose | Default / Notes |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPENID4VC_PROFILE` | Selects the ecosystem profile across wallet-as. | `DEFAULT` if unset. Supported values: `DEFAULT`, `WEBUILD`; see profiles below. |
| `SERVICE_URL` | Full external URL for the AS (scheme/host and optional path prefix). | `http://localhost:6060` if unset. Example: If deployed under `/as`, set it to `https://issuer.example.com/as`. |
| `WALLET_URL` | Redirect URI registered for the wallet client. | `http://localhost:3000` if unset. |
| `OIDC_JWKS_PATH` | Optional override for the OIDC provider signing JWKS path used for ID tokens and JWT authorization responses. | Defaults to `./keys/oidc.jwks.json` |
| `INTROSPECTION_CLIENT` | Client ID allowed to introspect tokens. | If set with `INTROSPECTION_CLIENT_SECRET`, an extra client is registered. |
| `INTROSPECTION_CLIENT_SECRET` | Secret for the introspection client. | Required alongside `INTROSPECTION_CLIENT`. |
| `SCOPES` | Comma-separated list of supported scopes. | Split by `,` and passed to OIDC provider `scopes`. |
| `METADATA_URL` | Credential Issuer metadata URL. | Used during consent to fetch display metadata for requested scopes. |
| `ACCESS_TOKEN_TTL` | Access token TTL (seconds). | Default: `30` |
| `REFRESH_TOKEN_TTL` | Refresh token TTL (seconds). | Default: `2592000` |
| `BASE_PATH` | Optional base path for public, AS/OIDC, and related Express endpoints. | Empty by default. Supports values like `as` or `/as`; normalized to `/as`. |
| `AUTHENTICATOR` | Selected authenticator to load (single value). | Supported values: `user-pass-pid` or `auth-broker`. Defaults to `user-pass-pid` if unset. |
| `USER_PASS_PID_DEMO_USERNAME` | Demo username for the `user-pass-pid` login screen and demo account. | Used only when `AUTHENTICATOR=user-pass-pid`. |
| `USER_PASS_PID_DEMO_PASSWORD` | Demo password shown in `user-pass-pid` login form. | Used only for prefill; authentication does not check password. |
| `AUTH_BROKER_PROVIDER_URL` | External OIDC provider URL for the `auth-broker` authenticator. | Required when `auth-broker` is enabled. |
| `AUTH_BROKER_CLIENT_ID` | OIDC client ID for the `auth-broker` authenticator. | Required when `auth-broker` is enabled. |
| `AUTH_BROKER_CLIENT_SECRET` | OIDC client secret for the `auth-broker` authenticator. | Optional for public clients. |
| `AUTH_BROKER_SCOPE` | Space-separated scopes sent to external IdP authorize endpoint by `auth-broker`. | Default: `openid profile email`. |
| `AUTH_BROKER_REDIRECT_URI` | Redirect URI handled by wallet-as auth-broker callback route. | Default: `SERVICE_URL + /interaction/authBroker/callback`. |
| `AUTH_BROKER_SKIP_LOGOUT` | Skip external IdP logout even if IdP supports it in metadata in `auth-broker` callback flow. | Optional; set to `true` when logout should be skipped. |
| `ABCA_CLIENT_ATTESTATION_MAX_AGE` | Maximum attestation age in seconds under `DEFAULT`; if `iat` is absent, limits remaining validity instead. | Defaults to `86400`. Ignored under `WEBUILD`. |
| `ABCA_CLIENT_ATTESTATION_POP_MAX_AGE` | Maximum PoP age in seconds under `DEFAULT`. Future PoP issuance times are rejected. | Defaults to `60`. Ignored under `WEBUILD`. |
| `ABCA_CHALLENGE_SECRET` | Shared secret for Attestation-Based Client Authentication challenges. | Required: exactly 64 hexadecimal characters encoding 32 random bytes. `yarn setup` in the parent repo generates a random one. Otherwise, use `openssl rand -hex 32` to generate a random value. |
| `DPOP_NONCE_SECRET` | Shared secret used to generate and validate DPoP nonces. | Required: at least 32 characters. `yarn setup` in the parent repo generates a random one. Otherwise, use `openssl rand -hex 16` to generate a random value. |
| `WEBUILD_ABCA_STATUS_LIST_ALLOWED_ORIGINS` | Comma-separated trusted origins for WIA status list requests under the WE BUILD profile. | Required at startup only when `OPENID4VC_PROFILE=WEBUILD`. HTTPS required; HTTP loopback allowed outside production. |

### Profiles

Set `OPENID4VC_PROFILE` in `.env` to select the ecosystem requirements applied by wallet-as:

| Value | Behavior |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `DEFAULT` | Default when unset. Applies the configurable attestation and PoP maximum ages, without WE BUILD checks or WIA status-list requests. |
| `WEBUILD` | Adds required wallet fields, WIA lifetime below 24 hours when `iat` is present, WIA/DPoP key equality, and revocation-status checks. |

## Authenticators

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@eslint/js": "^9.39.5",
"@types/express": "^5.0.6",
"@types/node": "^24.10.2",
"@types/oidc-provider": "^9.12.1",
"@types/oidc-provider": "^9.13.0",
"@types/supertest": "^6.0.3",
"eslint": "^9.39.5",
"eslint-config-prettier": "^10.1.8",
Expand All @@ -41,7 +41,7 @@
"dotenv": "^17.2.3",
"express": "^5.2.1",
"iovalkey": "^0.3.3",
"oidc-provider": "^9.12.2",
"oidc-provider": "^9.13.3",
"openid-client": "^6.8.2",
"pug": "^3.0.3",
"wallet-common": "git+https://github.com/wwWallet/wallet-common.git#v1.0.0",
Expand Down
42 changes: 40 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import {
import { dataStoreClient } from './stores/dataStoreClient';
import { createOidcValkeyAdapter } from './stores/OidcValkeyAdapter';
import preAuthorizedCodeHandler from './oid4vci/preAuthorizedCodeHandler';
import {
getAttestationSignaturePublicKey,
assertAttestationJwtAndPop,
} from './services/attestationBasedClientAuthenticationService';

export function createApp() {
const app = express();
Expand Down Expand Up @@ -48,7 +52,16 @@ export function createApp() {
adapter: createOidcValkeyAdapter(dataStoreClient),
clients: oidClients,
jwks: config.oidcJwks,

enabledJWA: {
attestSigningAlgValues: ['ES256'],
dPoPSigningAlgValues: ['ES256'],
},
clientAuthMethods: [
'client_secret_basic',
'client_secret_post',
'attest_jwt_client_auth',
'none',
],
discovery: {
...(config.preAuthorizedCredentialIssuance
? {
Expand All @@ -73,7 +86,20 @@ export function createApp() {
},
},
features: {
attestClientAuth: {
enabled: true,
challengeSecret: config.abcaChallengeSecret,
ack: 'draft-12',
getAttestationSignaturePublicKey,
assertAttestationJwtAndPop,
},
devInteractions: { enabled: false },
dPoP: {
enabled: true,
nonceSecret: config.dpopNonceSecret,
requireNonce: () => false,
allowReplay: false,
},
introspection: {
enabled: true,
allowedPolicy: introspectionAllowedPolicy,
Expand Down Expand Up @@ -148,7 +174,19 @@ export function createApp() {
);

routesRoot.post('/token', (req, _res, next) => {
if (req.body?.grant_type === 'urn:ietf:params:oauth:grant-type:pre-authorized_code') {
const hasClientAuthentication = Boolean(
req.body?.client_id ||
req.body?.client_secret ||
req.body?.client_assertion ||
req.headers.authorization ||
req.headers['oauth-client-attestation'] ||
req.headers['oauth-client-attestation-pop'],
);

if (
req.body?.grant_type === 'urn:ietf:params:oauth:grant-type:pre-authorized_code' &&
!hasClientAuthentication
) {
req.body.client_id = '__pre-authorized_code_client__';
}

Expand Down
Loading
Loading