Token authentication for iframe-embedded widgets using a backend proxy and the postMessage API. The user never sees a login screen - your page handles the token exchange behind the scenes.
The token authentication flow has four parts:
Create a server-side endpoint that calls the Returning.AI signin API with your secret API key and the authenticated user's email. This endpoint should be protected by your existing authentication middleware.
Keep your API key server-side
Never expose WIDGET_API_KEY in client-side code. It should only exist in environment variables on your backend. If compromised, rotate it immediately in the admin panel.
app.post('/api/widget-auth', async (req, res) => {
const userEmail = req.user.email;
try {
const response = await fetch(
'https://prod-widgets.returning.ai/widget/{community_id}/signin',
{
method: 'POST',
headers: {
'returningai-api-key': process.env.WIDGET_API_KEY,
'email': userEmail,
'Content-Type': 'application/json'
}
}
);
const data = await response.json();
res.json({ token: data.token });
} catch (error) {
res.status(500).json({ error: 'Authentication failed' });
}
})This script runs on page load. It calls your backend endpoint to obtain a token, stores it in localStorage, and listens for postMessage token requests from the widget iframe.
async function authenticateWidget() {
try {
const response = await fetch('/api/widget-auth', {
method: 'POST',
credentials: 'include'
});
const data = await response.json();
if (data.token) {
localStorage.setItem('returning-ai-widget-token', data.token);
console.log('Widget authenticated successfully');
}
} catch (error) {
console.error('Widget authentication failed:', error);
}
}
window.addEventListener('message', (event) => {
if (event.origin === 'https://prod-widgets.returning.ai') {
const { type } = event.data;
if (type === 'RETURNINGAI_WIDGET_REQUEST_TOKEN') {
const token = localStorage.getItem('returning-ai-widget-token');
event.source.postMessage(
{ type: 'RETURNINGAI_WIDGET_TOKEN', value: { token, widgetId: 'YOUR_WIDGET_ID' } },
event.origin
);
}
}
});
document.addEventListener('DOMContentLoaded', authenticateWidget);The widget iframe sends a RETURNINGAI_WIDGET_REQUEST_TOKEN message when it needs to authenticate or when the current token expires. The frontend script above already handles this - it validates the origin, retrieves the stored token, and responds with a RETURNINGAI_WIDGET_TOKEN message.
// The postMessage exchange looks like this:
// 1. Widget iframe → Your page:
{ type: 'RETURNINGAI_WIDGET_REQUEST_TOKEN' }
// 2. Your page → Widget iframe:
{
type: 'RETURNINGAI_WIDGET_TOKEN',
value: {
token: 'eyJhbGciOiJIUzI1NiIs...',
widgetId: 'YOUR_WIDGET_ID'
}
}Origin validation
Always check that event.origin matches https://prod-widgets.returning.ai before responding to postMessage events. This prevents other iframes or windows from intercepting the token.
The token is stored in localStorage under the key returning-ai-widget-token. This allows the token to persist across page navigations within the same session.
| Behavior | How it works |
|---|---|
| Initial load | Frontend script calls your backend, stores the token in localStorage. |
| Widget request | Widget iframe sends postMessage, your script responds with the stored token. |
| Token expiry | Widget sends another RETURNINGAI_WIDGET_REQUEST_TOKEN message. Your script should re-fetch from the backend and update localStorage. |
| User logout | Clear the token from localStorage when the user logs out of your platform. |
// Clear token on user logout
function onUserLogout() {
localStorage.removeItem('returning-ai-widget-token');
// Optionally reload the iframe to reset widget state
document.getElementById('returningAiWidgetIframe')?.contentWindow?.location.reload();
}Security checklist
event.origin in your postMessage listener.