Skip to main content

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-sdk package 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

  1. User clicks Sign in with mwen.io.
  2. authenticate() calls window.mwen.authenticate() on the extension, which opens the consent popup.
  3. User approves — the extension constructs an SD-JWT VP and returns it.
  4. The SDK verifies the VP locally (no network call), persists the session in localStorage, and schedules auto-refresh.
  5. useMwen() re-renders with the populated session.

Next steps