Configuration
MwenClientConfig is the single configuration object passed to MwenClient (or MwenProvider). This page documents every field, its default, and guidance on what to set.
MwenClientConfig
interface MwenClientConfig {
clientId: string;
appName: string;
scopes?: readonly string[];
revocationPath?: string;
relayPath?: string;
verifyPath?: string;
sessionTtlMs?: number;
// Advanced — issued credential gating
vct?: string;
vctRequired?: boolean;
onRefreshError?: (error: Error) => void;
onCredentialNotFound?: (error: MwenCredentialNotFoundError) => AuthenticateResult | null | undefined | Promise<AuthenticateResult | null | undefined>;
onCredentialRevoked?: (error: MwenCredentialRevokedError) => AuthenticateResult | null | undefined | Promise<AuthenticateResult | null | undefined>;
}
Fields
clientId — required
clientId: string
Your application's identifier. This is the value used as:
- The
client_idin the auth request sent to the extension. - The HKDF
infoinput for per-app key derivation — the user'sdid:jwkidentifier is derived from this value. - The
audienceclaim that server-side token verification checks against.
Use your app's origin domain — e.g. "shop.example.com" or "myapp.io". Do not include https:// or a trailing slash.
For local development, use "localhost:3001" (or whatever port you are running on). The user will get a different did:jwk for localhost:3001 vs. shop.example.com — keep this consistent in your environment config.
clientId: process.env.NEXT_PUBLIC_CLIENT_ID ?? 'localhost:3001',
Important: Changing
clientIdin production means all existing users will have a differentdid:jwkthe next time they sign in. They will appear as new users to your database. Do not change this value after go-live.
appName — required
appName: string
Human-readable display name shown to the user on the consent screen inside the extension popup. Maximum 100 characters.
appName: 'Acme Wine & Spirits',
scopes — optional
scopes?: readonly string[]
// Default: ['openid', 'nickname', 'name', 'age']
The identity claims you want to request from the user. The extension consent screen will show only the scopes you list here.
Always include 'openid' as the first scope. It is required by the protocol and carries no claims itself.
| Scope | Claims produced | Notes |
|---|---|---|
openid | (none) | Required |
identity | sub (did:jwk) | Always included; listing it explicitly is optional |
name | given_name, family_name | |
nickname | nickname | |
age | age_over_18, age_over_13, age_over_21 | BBS+ ZK proofs — birth date never revealed |
birthdate | birth_date | Raw date — user can hide |
country | country | ISO 3166-1 alpha-2 |
email | email, email_verified | |
phone | phone_number, phone_verified | |
address | street_address, locality, region, postal_code |
Request only the scopes your app actually needs. Requesting unnecessary claims reduces user trust and increases the probability of denial.
// Age-gated content: ask for pseudonymous ID + age proof only
scopes: ['openid', 'age'],
// E-commerce checkout: ask for name, email, and address
scopes: ['openid', 'name', 'email', 'address'],
revocationPath — optional
revocationPath?: string
// Default: '/revoked'
The path (relative to your app's origin) of the page that handles disconnect notifications from the wallet. When a user disconnects your app from their wallet, the extension opens a browser tab at ${origin}${revocationPath}#appIdentity=<did>&signature=<jws>.
Your /revoked page should:
- Strip the
#fragmentfrom the browser history. - Extract
appIdentityandsignaturefrom the fragment. POSTthem to your API to terminate the session.
See Handling Revocation for the full implementation.
relayPath — optional
relayPath?: string
// Default: '/api/mwen/relay'
The path to your app's relay endpoint. Used by requestAsyncAttestation() and included in client_metadata so the extension knows where to deliver async credential updates.
You must implement the relay endpoints for this to work. See Async Attestations & Relay.
verifyPath — optional
verifyPath?: string
// Default: '/api/verify'
The path to your app's verify endpoint. The extension sends the SD-JWT VP response to ${origin}${verifyPath} via direct_post. This endpoint receives, verifies, and processes the credential.
In the SDK's current Path A (extension) flow, verifyPath is included in the auth request metadata but the response comes back as a Promise — your /api/verify endpoint may still be called by the extension as a secondary delivery mechanism depending on extension version.
sessionTtlMs — optional
sessionTtlMs?: number
// Default: 86_400_000 (24 hours)
Maximum age in milliseconds of a VerifiedSession stored in localStorage. Sessions older than this are treated as expired and getSession() returns null.
This is separate from the credential's own expiresAt — both are checked. The shorter of the two takes precedence.
// 8 hours
sessionTtlMs: 8 * 60 * 60 * 1000,
vct — optional
vct?: string
// Default: '' (disabled)
A Verifiable Credential Type URI. When set, the wallet attempts to find a stored issued credential whose vct matches this URI and uses it as the basis of the auth response, instead of relying solely on self-attested profile data.
The vct URI is defined by the issuer. You can find the correct URI in the issuer's credential schema documentation — for mwen.io-hosted issuers, see the Credential Schemas reference.
vct: 'https://mwen.io/schemas/employee-identity/v1',
Only meaningful when your app is partnered with an issuer and you want to gate access or unlock features based on a specific issued credential. See Credential Gating for the full pattern.
vctRequired — optional
vctRequired?: boolean
// Default: true
Controls what happens when vct is set but no matching credential is found in the user's wallet, or the credential has been revoked.
true(default) — authentication fails hard.authenticate()throwsMwenCredentialNotFoundErrororMwenCredentialRevokedError. Use this when the credential is a strict access requirement.false— the wallet falls back to self-attestation.authenticate()resolves normally. UseAuthenticateResult.presentedCredentialsto determine whether the credential was actually presented.
Only meaningful when
vctis set. Ignored otherwise.
See Credential Gating for usage examples.
onRefreshError — optional
onRefreshError?: (error: Error) => void
Called when the auto-refresh timer fails after one retry attempt. The auto-refresh runs at 75% of the credential's remaining lifetime — if both the initial attempt and the 5-second retry fail, this callback is invoked with the final error.
Use this to surface refresh failures to the UI without coupling MwenClient to a specific framework. Common patterns:
// Show a toast and prompt re-authentication
onRefreshError: (error) => {
console.error('Session refresh failed:', error);
showToast('Your session has expired. Please sign in again.');
},
If not provided, refresh errors are silently swallowed after the retry.
onCredentialNotFound — optional
onCredentialNotFound?: (
error: MwenCredentialNotFoundError
) => AuthenticateResult | null | undefined | Promise<AuthenticateResult | null | undefined>
Called when vct is set, vctRequired: true, and no stored credential matches the requested vct. The return value controls whether the error propagates:
- Return an
AuthenticateResult— used as the resolved value ofauthenticate(). TheMwenCredentialNotFoundErroris not thrown. Use this to complete an alternative flow inline (e.g. open the issuer enrolment portal and retry authentication after the user obtains the credential). - Return
null,undefined, or nothing — theMwenCredentialNotFoundErroris thrown as normal, giving the caller the opportunity to redirect or gate the UI.
The callback may be async.
onCredentialNotFound: async () => {
// Open your issuer's enrolment portal, wait for completion,
// then retry — the user should now have the credential.
await openEnrolmentFlow();
return client.authenticate();
},
onCredentialRevoked — optional
onCredentialRevoked?: (
error: MwenCredentialRevokedError
) => AuthenticateResult | null | undefined | Promise<AuthenticateResult | null | undefined>
Called when vct is set, vctRequired: true, and the matching credential has been revoked by the issuer. Follows the same return-value semantics as onCredentialNotFound.
onCredentialRevoked: async () => {
// Guide the user through re-issuance with the issuer,
// then retry authentication.
await openReissuanceFlow();
return client.authenticate();
},
Full example
const mwenConfig: MwenClientConfig = {
clientId: process.env.NEXT_PUBLIC_CLIENT_ID ?? 'localhost:3001',
appName: 'Acme Wine & Spirits',
scopes: ['openid', 'name', 'age', 'email'],
revocationPath: '/revoked',
relayPath: '/api/mwen/relay',
verifyPath: '/api/verify',
sessionTtlMs: 4 * 60 * 60 * 1000, // 4 hours
};
Advanced example — credential gating
This example requires users to hold a specific issued credential. If they do not, the onCredentialNotFound callback opens an enrolment flow before retrying.
const client = new MwenClient({
clientId: process.env.NEXT_PUBLIC_CLIENT_ID ?? 'localhost:3001',
appName: 'Acme Employee Portal',
scopes: ['openid', 'name', 'email'],
vct: 'https://mwen.io/schemas/employee-identity/v1',
vctRequired: true,
onCredentialNotFound: async () => {
// Redirect to the issuer's enrolment portal. After the user
// obtains their credential, retry authentication.
await openEnrolmentFlow('https://issuer.acme.com/enrol');
return client.authenticate();
},
onCredentialRevoked: async () => {
await openReissuanceFlow('https://issuer.acme.com/renew');
return client.authenticate();
},
onRefreshError: (error) => {
console.error('Auto-refresh failed:', error);
showToast('Session expired — please sign in again.');
},
});
See Credential Gating for the full pattern including soft gating and reading presentedCredentials.