Skip to main content

Issued Credentials

When a user holds issued credentials — credentials from a government agency, employer, university, or learning platform — the access token your app receives includes a trust_level claim, and additional issuerCategory and issuerDID claims when the credential was externally issued.

This page explains how to read these claims and what they mean for your application.


How trust level selection works

A mwen.io user may hold both self-attested credentials and issued credentials from external organisations. The wallet selects the best available credential at sign-in time based on trust level.

The trust level hierarchy is:

"government" > "verified-issuer" > "self-attested"

The wallet always prefers the highest available trust level for each requested claim. Your app does not need to do anything special — the wallet handles selection automatically. What changes for you is the claims you receive.

If your app needs to require a specific issued credential type rather than accepting the wallet's automatic best-available selection, see Credential Gating.


Claims in the access token payload

The access token payload always includes trust_level. When the credential was issued by an external organisation, issuerCategory and issuerDID are also present:

ClaimTypePresent whenDescription
trust_levelstringAlways"government", "verified-issuer", or "self-attested"
issuerCategorystringWhen trust_level !== "self-attested""government", "employer", "academic", or "learning-platform"
issuerDIDstringWhen trust_level !== "self-attested"The did:web of the issuing organisation (e.g. did:web:issuer.acme.com)

Example payload — government credential

{
"sub": "did:jwk:eyJ...",
"iss": "did:jwk:eyJ...",
"aud": "myapp.com",
"iat": 1741600000,
"exp": 1741603600,
"scope": "openid identity name",
"given_name": "Alice",
"family_name": "Smith",
"trust_level": "government",
"issuerCategory": "government",
"issuerDID": "did:web:issuer.gov.tt"
}

Example payload — self-attested

{
"sub": "did:jwk:eyJ...",
"iss": "did:jwk:eyJ...",
"aud": "myapp.com",
"iat": 1741600000,
"exp": 1741603600,
"scope": "openid identity name",
"given_name": "Alice",
"family_name": "Smith",
"trust_level": "self-attested"
}

Reading trust level and issuer category

Use verifyAccessTokenFromHeader() as normal — the new claims are included in the verified payload:

import { verifyAccessTokenFromHeader } from '@mwen/js-sdk/server';

export async function GET(req: Request) {
const result = await verifyAccessTokenFromHeader(
req.headers.get('authorization') ?? '',
'myapp.com'
);

if (!result.valid) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}

const { sub, trust_level, issuerCategory, issuerDID } = result.payload!;

// sub is the stable per-app user identifier (did:jwk)
// trust_level is always present
// issuerCategory and issuerDID are present only for verified credentials
}

Gating features on trust level

A common pattern is to unlock features or reduce friction for users with higher trust level credentials.

function getUserTier(trustLevel: string, issuerCategory?: string) {
if (trustLevel === 'government') return 'verified-identity';
if (trustLevel === 'verified-issuer') {
if (issuerCategory === 'employer') return 'employee';
if (issuerCategory === 'academic') return 'student';
return 'verified';
}
return 'basic';
}

const tier = getUserTier(result.payload!.trust_level, result.payload!.issuerCategory);

// Examples:
// Allow government-verified users to skip manual KYC
if (result.payload!.trust_level === 'government') {
// Skip KYC step — identity already government-verified
}

// Allow employer-verified users to access corporate content
if (result.payload!.issuerCategory === 'employer') {
// Grant enterprise tier access
}

Verifying the issuer DID

If your application needs to accept credentials only from a specific organisation (e.g. only from your company's own issuer), verify the issuerDID claim:

const TRUSTED_ISSUER = 'did:web:issuer.acme.com';

if (result.payload!.issuerDID !== TRUSTED_ISSUER) {
return Response.json({ error: 'Credential from unknown issuer' }, { status: 403 });
}

This is optional — most apps trust any issuer in the mwen.io trust registry and gate access on trust_level or issuerCategory alone.


Revoked credentials

The wallet checks revocation status before including a credential in the access token. A revoked credential is never presented to your app. If the user's only credential for a requested claim is revoked, the wallet falls back to the next trust level.

From your app's perspective:

  • You will never receive a revoked credential in the access token.
  • If a user's trust level drops (e.g. from verified-issuer to self-attested after revocation), your app should handle this gracefully — the user is still who they say they are, just at a lower assurance level.
// Check whether a user's session should trigger re-verification
if (previousTrustLevel === 'verified-issuer' && currentTrustLevel === 'self-attested') {
// The credential was revoked. Prompt re-verification or adjust access accordingly.
}