Sessions & Token Handling
This page covers the VerifiedSession lifecycle — how it is created, stored, refreshed, expired, and cleared — and how to work with the access and refresh tokens it contains.
The VerifiedSession object
After a successful authenticate() call, the SDK persists a VerifiedSession to localStorage under the key mwen_verified_session. Its shape:
interface VerifiedSession {
appIdentity: string; // did:jwk — your stable user identifier
verified: true;
scopes: string[];
holder: string; // same as appIdentity
timestamp: number; // session creation time (Unix ms)
expiresAt?: number; // credential expiry (Unix ms)
access_token?: string; // signed AT+JWT (3600 s TTL by default)
refresh_token?: string; // signed RT+JWT (86400 s TTL, rotated on use)
issuerDID?: string;
claims?: Record<string, { value: unknown; format: string; zkProof?: boolean }>;
disclosedClaims?: string[];
}
appIdentity is the stable per-app did:jwk — use it as the primary key for this user in your own database.
Restoring a session on page load
On page load, call getSession() (or rely on MwenProvider to do it automatically) to restore an existing session without re-authenticating:
// Framework-agnostic
const session = client.getSession();
if (session) {
// Restore UI state from session
}
// React — handled automatically by MwenProvider
const { session } = useMwen();
// session is non-null if a valid session exists in localStorage
getSession() returns null when:
- No session exists in
localStorage. - The session's
timestampis older thansessionTtlMs(default: 1 hour). - The session's
expiresAtis in the past.
Auto-refresh
The SDK schedules a proactive credential refresh at 75% of the remaining credential lifetime. For a credential with a 1-hour TTL:
- At 45 minutes,
refreshCredential()is called automatically. - If a delegation grant is active, this happens silently — no popup shown.
- If no grant is active, the refresh falls back to a full
authenticate()(popup shown). - If the refresh fails, the session is cleared. Your app should respond to
sessionbecomingnull.
The timer is started after authenticate() or refreshCredential() and cancelled by signOut().
Access tokens
session.access_token is a signed AT+JWT with a 3600-second (1 hour) TTL. Send it in the Authorization header for protected API requests:
const session = client.getSession();
const response = await fetch('/api/protected', {
headers: {
Authorization: `Bearer ${session?.access_token}`,
},
});
On the server, verify it with verifyAccessTokenFromHeader(). See Verifying Credentials Server-Side.
Refresh tokens
session.refresh_token is a signed RT+JWT with an 86400-second (24 hour) TTL. It is rotated on every use — each refresh produces a new refresh token and invalidates the old one.
The SDK manages refresh tokens internally. You do not need to send or store them manually — they are read from localStorage during refreshCredential().
Cross-tab revocation
MwenProvider listens for the storage event on window. When any tab clears the mwen_verified_session key (e.g. the user signs out in another tab, or the extension fires a revocation), all other tabs update their state immediately:
// This is handled automatically inside MwenProvider.
// Your UI will re-render with session = null if another tab signs out.
If you are using MwenClient without MwenProvider, set up the listener manually:
window.addEventListener('storage', (e) => {
if (e.key === 'mwen_verified_session' && e.newValue === null) {
// Session was cleared in another tab
handleSignOut();
}
});
Explicit sign-out
// React
const { signOut } = useMwen();
<button onClick={signOut}>Sign out</button>
// Framework-agnostic
client.signOut();
signOut():
- Cancels the auto-refresh timer.
- Removes
mwen_verified_sessionfromlocalStorage. - The
storageevent fires — other tabs are notified.
Session key constant
The localStorage key is exported for cases where you need to read or watch it directly:
import { VERIFIED_SESSION_KEY } from '@mwen/js-sdk';
// 'mwen_verified_session'