Next.js Integration
Embed authentication in a Next.js App Router project — a client component for the iframe and a Route Handler that verifies the token and sets a first-party session cookie.
Next.js splits cleanly along the EmbedAuth boundary: the iframe and its message listener are client concerns, while token verification and session creation belong in a server Route Handler. This guide wires both.
The client component
postMessage requires the browser, so the embed is a client component.
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
const CLIENT_ID = 'YOUR_CLIENT_ID';
const EMBED_URL = 'https://embedauth.com';
// The embed URL redirects to the widget's copy on the CDN, so its messages
// come from the CDN's origin.
const EMBED_ORIGIN = 'https://cdn.embedauth.com';
export function AuthEmbed({ mode = 'signin' }: { mode?: string }) {
const router = useRouter();
useEffect(() => {
async function onMessage(event: MessageEvent) {
if (event.origin !== EMBED_ORIGIN) return;
if (event.data?.type !== 'authframe:auth-success') return;
const res = await fetch('/api/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: event.data.data.token }),
});
if (res.ok) router.push('/dashboard');
}
window.addEventListener('message', onMessage);
return () => window.removeEventListener('message', onMessage);
}, [router]);
return (
<iframe
title="Sign in"
src={`${EMBED_URL}/embed/${CLIENT_ID}?mode=${mode}`}
width={420}
height={600}
style={{ border: 'none', width: '100%', maxWidth: 420 }}
/>
);
}
The Route Handler
Verify the EmbedAuth token server-side, then mint your own session cookie. Never store the raw EmbedAuth token in a long-lived cookie — treat it as a short-lived proof of identity and issue a first-party session you control.
// app/api/session/route.ts
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { createRemoteJWKSet, jwtVerify, SignJWT } from 'jose';
const CLIENT_ID = process.env.EMBEDAUTH_CLIENT_ID!;
const SESSION_SECRET = new TextEncoder().encode(process.env.SESSION_SECRET!);
const JWKS = createRemoteJWKSet(
new URL(`https://embedauth.com/.well-known/jwks.json?clientId=${CLIENT_ID}`)
);
export async function POST(request: Request) {
const { token } = await request.json();
let payload;
try {
({ payload } = await jwtVerify(token, JWKS, {
issuer: 'https://embedauth.com',
audience: CLIENT_ID,
}));
} catch {
return NextResponse.json({ error: 'invalid_token' }, { status: 401 });
}
// Upsert the user in your DB keyed on payload.sub, then issue a session
const session = await new SignJWT({ sub: payload.sub, email: payload.email })
.setProtectedHeader({ alg: 'HS256' })
.setExpirationTime('7d')
.sign(SESSION_SECRET);
(await cookies()).set('session', session, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 60 * 60 * 24 * 7,
});
return NextResponse.json({ ok: true });
}
Reading the session in Server Components
Because the session is a first-party, httpOnly cookie, you can read it in layouts and Server Components without exposing anything to the client:
import { cookies } from 'next/headers';
import { jwtVerify } from 'jose';
export async function getSession() {
const token = (await cookies()).get('session')?.value;
if (!token) return null;
try {
const { payload } = await jwtVerify(
token,
new TextEncoder().encode(process.env.SESSION_SECRET!)
);
return payload;
} catch {
return null;
}
}
Content Security Policy
Next.js is often deployed with a strict CSP. Add the EmbedAuth origin to frame-src in your next.config.js headers or middleware, or the iframe silently fails to load in production. The exact directive is in the Content Security Policy guide.
Next steps
- Backend verification details and error handling: Verifying JWTs in Node.js.
- Not using App Router? The same
/api/sessionverification pattern works frompages/api. - Frame not appearing? Start with Troubleshooting.