Frontend Integration

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.

1 min readUpdated October 5, 2026

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/session verification pattern works from pages/api.
  • Frame not appearing? Start with Troubleshooting.