React Integration
Mount the EmbedAuth iframe in a React app with a reusable component and a custom hook that handles the postMessage lifecycle and cleanup.
This guide wraps the EmbedAuth embed in an idiomatic React component. The pattern is small: an <iframe>, a message listener registered in useEffect, and clean teardown so you never leak listeners across renders.
A reusable hook
The subtle part of any postMessage integration is lifecycle: you must add exactly one listener, verify the origin, and remove the listener on unmount. A hook keeps that logic in one place.
import { useEffect } from 'react';
// The embed URL on embedauth.com 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';
type AuthUser = { id: string; email: string; name?: string };
type AuthSuccess = { token: string; refreshToken: string; user: AuthUser };
export function useEmbedAuth(onSuccess: (result: AuthSuccess) => void) {
useEffect(() => {
function handleMessage(event: MessageEvent) {
if (event.origin !== EMBED_ORIGIN) return;
const message = event.data;
if (message?.type === 'authframe:auth-success') {
onSuccess(message.data as AuthSuccess);
}
}
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [onSuccess]);
}
Wrap onSuccess in useCallback at the call site (or keep it stable) so the effect does not re-subscribe on every render.
The embed component
import { useCallback } from 'react';
import { useEmbedAuth } from './useEmbedAuth';
const CLIENT_ID = 'YOUR_CLIENT_ID';
export function AuthEmbed({ mode = 'signin' }: { mode?: 'signin' | 'signup' | 'forgot-password' }) {
const handleSuccess = useCallback(({ token, user }) => {
// Send the token to your API to establish a first-party session
fetch('/api/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
}).then(() => {
window.location.assign('/dashboard');
});
console.log('Signed in as', user.email);
}, []);
useEmbedAuth(handleSuccess);
const src = `https://embedauth.com/embed/${CLIENT_ID}?mode=${mode}`;
return (
<iframe
title="Sign in"
src={src}
width={420}
height={600}
style={{ border: 'none', width: '100%', maxWidth: 420 }}
/>
);
}
Auto-resizing the frame
The embed emits an authframe:resize event with its content height. Track it in state to avoid inner scrollbars:
import { useEffect, useState } from 'react';
export function useEmbedHeight(fallback = 600) {
const [height, setHeight] = useState(fallback);
useEffect(() => {
function onMessage(event: MessageEvent) {
if (event.origin !== 'https://cdn.embedauth.com') return;
if (event.data?.type === 'authframe:resize') {
setHeight(event.data.data.height);
}
}
window.addEventListener('message', onMessage);
return () => window.removeEventListener('message', onMessage);
}, []);
return height;
}
Pass the returned height to the iframe's height prop and the frame grows and shrinks with the form (sign-up is taller than sign-in, error states add rows, and so on).
Handling errors
Listen for authframe:auth-error alongside success so you can surface a message or trigger analytics. The payload is { error: string, code: string }. Do not try to recover the user's input — it lives inside the iframe by design.
Production notes
- Verify the token on your server, never in the browser. The client hook is only for UX; trust is established in your API. See Verifying JWTs in Node.js.
- Add the EmbedAuth origin to your app's Content Security Policy
frame-src, or the iframe will be blocked in production. - Using the App Router? See the Next.js Integration guide for the
'use client'boundary and route-handler session pattern.