Quickstart
This guide shows how to add "Sign in with mwen.io" to a Next.js / React application in four steps. The pattern mirrors the reference implementation in apps/consumer-app.
Prerequisites
- Node.js 18+
- A React application (Next.js 14+ recommended)
- The
@mwen/js-sdkpackage available in your workspace
The package is currently distributed as a private monorepo package. Public npm publication is planned for Phase 9. For now, reference it via the monorepo path alias
@mwen/js-sdk.
Step 1 — Wrap your app in MwenProvider
MwenProvider initialises the SDK, detects the extension, and exposes the auth state to all child components.
// app/providers.tsx
'use client';
import { MwenProvider } from '@mwen/js-sdk/react';
import type { MwenClientConfig } from '@mwen/js-sdk';
const mwenConfig: MwenClientConfig = {
clientId: process.env.NEXT_PUBLIC_CLIENT_ID ?? 'localhost:3001',
appName: 'My App',
scopes: ['openid', 'name', 'age', 'email'],
revocationPath: '/revoked',
relayPath: '/api/mwen/relay',
verifyPath: '/api/verify',
};
export default function Providers({ children }: { children: React.ReactNode }) {
return <MwenProvider config={mwenConfig}>{children}</MwenProvider>;
}
// app/layout.tsx
import Providers from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
Step 2 — Add a sign-in button
Use the useMwen() hook to access auth state and trigger authentication.
// components/SignInButton.tsx
'use client';
import { useMwen } from '@mwen/js-sdk/react';
export default function SignInButton() {
const { session, hasExtension, authenticate, signOut, isLoading, error } = useMwen();
if (isLoading) return <p>Checking extension...</p>;
if (session) {
return (
<div>
<p>Signed in as {session.appIdentity}</p>
<button onClick={signOut}>Sign out</button>
</div>
);
}
if (!hasExtension) {
return (
<a href="https://chrome.google.com/webstore" target="_blank">
Install mwen.io to sign in
</a>
);
}
return (
<div>
<button onClick={authenticate} disabled={isLoading}>
Sign in with mwen.io
</button>
{error && <p role="alert">{error.message}</p>}
</div>
);
}
Step 3 — Read session data
After sign-in, session is a VerifiedSession object. Use it to display user info and personalise your app.
'use client';
import { useMwen } from '@mwen/js-sdk/react';
export default function ProfilePanel() {
const { session } = useMwen();
if (!session) return null;
const name = session.claims?.given_name?.value as string | undefined;
const ageOk = session.claims?.age_over_18?.value as boolean | undefined;
return (
<div>
{name && <p>Welcome, {name}</p>}
{ageOk && <p>Age verified (18+)</p>}
<p>User ID: {session.appIdentity}</p>
</div>
);
}
Step 4 — Protect API routes
On the server, use verifyAccessTokenFromHeader to authenticate requests made with the session's access_token.
// app/api/protected/route.ts
import { verifyAccessTokenFromHeader } from '@mwen/js-sdk/server';
import { NextRequest, NextResponse } from 'next/server';
export async function GET(req: NextRequest) {
const result = await verifyAccessTokenFromHeader(
req.headers.get('authorization') ?? '',
'myapp.com' // must match your clientId
);
if (!result.valid) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// result.payload.sub is the user's did:jwk identifier
return NextResponse.json({ message: `Hello ${result.payload.sub}` });
}
What happens at runtime
- User clicks Sign in with mwen.io.
authenticate()callswindow.mwen.authenticate()on the extension, which opens the consent popup.- User approves — the extension constructs an SD-JWT VP and returns it.
- The SDK verifies the VP locally (no network call), persists the session in
localStorage, and schedules auto-refresh. useMwen()re-renders with the populatedsession.
Next steps
- SDK Reference — full API documentation
- Configuration — all
MwenClientConfigoptions - Scopes & Claims — what claims are available
- Verifying Credentials — server-side verification in depth