Skip to main content

Credential Gating

By default, the mwen.io wallet automatically selects the best available credential for each authentication request. Credential gating lets your app go further: you can require that the user holds a specific issued credential type — identified by a vct URI — as a condition of sign-in or as a signal for conditional UI.

This is an advanced pattern. Most apps do not need it. If you only want to read trust level and issuer information from a signed-in user, see Issued Credentials instead.


When to use credential gating

  • Employee portals — only allow sign-in for users who hold a corporate identity credential from your issuer.
  • Hard KYC bypass — require a government-issued credential to unlock identity-verified features, blocking users who have not obtained one.
  • Soft enrichment — attempt to use an issued credential when available, but fall back gracefully for users who have not yet enrolled. Show different UI in each case.
  • Audit trails — record which specific credential type was presented at sign-in for compliance purposes.

The vct URI

Each issued credential schema has a globally unique vct (Verifiable Credential Type) URI defined by the issuer. For mwen.io-hosted issuers, these are listed in the Credential Schemas reference. Examples:

Credential typevct URI
Employee Identityhttps://mwen.io/schemas/employee-identity/v1
National IDhttps://mwen.io/schemas/national-id/v1
University Diplomahttps://mwen.io/schemas/university-diploma/v1
Course Completionhttps://mwen.io/schemas/course-completion/v1

Set vct in MwenClientConfig to activate credential gating:

const client = new MwenClient({
clientId: 'portal.acme.com',
appName: 'Acme Employee Portal',
scopes: ['openid', 'name', 'email'],
vct: 'https://mwen.io/schemas/employee-identity/v1',
});

Hard gating — vctRequired: true (default)

With the default vctRequired: true, authentication fails immediately if the user does not hold a matching credential. authenticate() throws:

  • MwenCredentialNotFoundError — no credential of the requested vct is stored in the wallet.
  • MwenCredentialRevokedError — a matching credential exists but has been revoked by the issuer.

Plain try/catch

import {
MwenClient,
MwenCredentialNotFoundError,
MwenCredentialRevokedError,
} from '@mwen/js-sdk';

const client = new MwenClient({
clientId: 'portal.acme.com',
appName: 'Acme Employee Portal',
scopes: ['openid', 'name', 'email'],
vct: 'https://mwen.io/schemas/employee-identity/v1',
vctRequired: true,
});

try {
const result = await client.authenticate();
// User holds a valid employee identity credential — proceed
} catch (error) {
if (error instanceof MwenCredentialNotFoundError) {
// User has not enrolled — redirect to the issuer enrolment portal
window.location.href = 'https://issuer.acme.com/enrol';
} else if (error instanceof MwenCredentialRevokedError) {
// Credential was revoked — guide the user through re-issuance
window.location.href = 'https://issuer.acme.com/renew';
} else {
throw error;
}
}

Inline callbacks

As an alternative to wrapping every authenticate() call in try/catch, you can handle credential errors inline using onCredentialNotFound and onCredentialRevoked. Returning an AuthenticateResult from the callback causes authenticate() to resolve with that value instead of throwing.

const client = new MwenClient({
clientId: 'portal.acme.com',
appName: 'Acme Employee Portal',
scopes: ['openid', 'name', 'email'],
vct: 'https://mwen.io/schemas/employee-identity/v1',
vctRequired: true,

onCredentialNotFound: async () => {
// Open the issuer's enrolment flow (e.g. in a modal or new tab).
// Once the user has 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();
},
});

// No try/catch needed — the callbacks handle both error cases.
const result = await client.authenticate();

If the callback returns null, undefined, or nothing, the error is re-thrown as normal.

React pattern

In React, attach the callbacks at the MwenProvider level so every authenticate() call in the component tree benefits from them automatically:

import { MwenProvider } from '@mwen/js-sdk/react';

const config = {
clientId: 'portal.acme.com',
appName: 'Acme Employee Portal',
scopes: ['openid', 'name', 'email'],
vct: 'https://mwen.io/schemas/employee-identity/v1',
vctRequired: true,
onCredentialNotFound: async () => {
await openEnrolmentFlow('https://issuer.acme.com/enrol');
// client is accessible via useMwen() inside the callback if needed
},
};

export function App() {
return (
<MwenProvider config={config}>
<YourApp />
</MwenProvider>
);
}

Soft gating — vctRequired: false

With vctRequired: false, the wallet attempts the credential lookup but does not fail if the credential is missing or revoked. It falls back to self-attestation and authenticate() resolves normally either way. No error is thrown.

Use AuthenticateResult.presentedCredentials to determine what was actually presented:

const client = new MwenClient({
clientId: 'myapp.com',
appName: 'My App',
scopes: ['openid', 'name', 'email'],
vct: 'https://mwen.io/schemas/employee-identity/v1',
vctRequired: false, // fall back to self-attestation if credential unavailable
});

const result = await client.authenticate();

if (result.presentedCredentials.length > 0) {
// The wallet found and presented the employee identity credential.
// Unlock features that require verified employment.
showEmployeeDashboard();
} else {
// Fell back to self-attestation — the user has not yet enrolled,
// or their credential was revoked. Show a prompt.
showEnrolmentPrompt('Get your employee credential to unlock full access.');
}

Soft gating is useful when the credential enhances the experience but the app remains functional without it — for example pre-filling verified employment details, or displaying a "verified employee" badge.


requestedCredentials and presentedCredentials

Both fields are always present on AuthenticateResult.

FieldTypeValue
requestedCredentialsstring[]Contains config.vct when set, otherwise []. Useful for audit logging.
presentedCredentialsstring[]VCT URIs of credentials actually presented by the wallet. Empty when the wallet used self-attestation (either because no vct was set, or vctRequired: false and the credential was unavailable).
const result = await client.authenticate();

// Log for audit / analytics
console.log('Requested:', result.requestedCredentials);
// e.g. ['https://mwen.io/schemas/employee-identity/v1']

console.log('Presented:', result.presentedCredentials);
// e.g. ['https://mwen.io/schemas/employee-identity/v1'] — credential found
// e.g. [] — self-attestation fallback (vctRequired: false)

When vctRequired: true and the credential was found, requestedCredentials and presentedCredentials will contain the same URI. When vctRequired: false and the credential was unavailable, requestedCredentials will contain the URI and presentedCredentials will be empty.


  • Configuration — full MwenClientConfig reference including vct, vctRequired, and the callback fields
  • SDK ReferenceAuthenticateResult type and error classes
  • Issued Credentials — reading trust_level, issuerDID, and issuerCategory from the access token
  • Credential Schemasvct URI reference for mwen.io-hosted issuers