JWT & Token Security
A summary of how tokens move through the browser, what the host page can and cannot access, and what you need to do — and not do — to keep the integration secure on the frontend.
What passes through the browser
The Relay authentication flow involves two tokens. Understanding what each one is and where it lives is the foundation of the frontend security model.
| Token | Created by | Passes through browser? | Lifetime | Can host page access it? |
|---|---|---|---|---|
| Assertion JWT | Your backend | Yes — your frontend fetches it from your backend and passes it to Fruga.init() | 60 seconds (max 120) | Yes — it is passed as a plain string to the SDK |
| Fruga access token | Fruga | No — exchanged and stored entirely within the widget iframe | 15 minutes, auto-refreshed | No — isolated by browser same-origin policy |
The assertion is the only sensitive token your page handles directly, and its exposure window is narrow by design — it expires in 60 seconds and can only be used once.
iframe isolation
The Relay widget runs inside a cross-origin iframe served from Fruga’s domain. This means your host page and the widget are subject to the browser’s same-origin policy — they cannot read each other’s DOM, cookies, local storage, or JavaScript context.
In practice, this has two important implications:
Your host page cannot access the Fruga access token. Even if a user opens DevTools, the token is not accessible from the host page context. It lives inside the iframe and never crosses the boundary.
The widget cannot access anything on your host page. The widget cannot read your cookies, inspect your local storage, or execute JavaScript in your page context. The iframe boundary is a strict security boundary in both directions.
Keeping the assertion secure
Although the assertion is short-lived and single-use, there are a few simple practices that keep it as safe as possible on the frontend.
Never cache the assertion
Fetch a fresh assertion from your backend each time the widget initialises or refreshes. Do not store the assertion in local storage, session storage, or any persistent browser state. A cached assertion that has been used before will fail the replay check and return a 409; a cached assertion that has not been used is a small but unnecessary exposure.
Never log the assertion
Avoid logging the assertion string to your browser console or sending it to any analytics or error monitoring service. Although it expires quickly, treat it as you would any short-lived credential.
Generate it as close to use as possible
Your backend should generate the assertion immediately before returning it to the browser — not ahead of time and then cached server-side. The 60-second window begins at generation, not at first use.
Content Security Policy
If your site uses a Content Security Policy, you will need to allow the Fruga widget iframe and its associated resources. Add the following directives to your existing CSP header.
Content-Security-Policy:
frame-src https://widget.fruga.com;
script-src https://cdn.fruga.com;
connect-src https://api.fruga.com;
| Directive | Domain | Why it is needed |
|---|---|---|
frame-src | https://widget.fruga.com | Allows the widget iframe to load |
script-src | https://cdn.fruga.com | Allows the SDK script tag to load |
connect-src | https://api.fruga.com | Allows the SDK to make API calls to Fruga (from within the iframe context) |
Summary
| Practice | Required? |
|---|---|
| Keep Signing Secret server-side only | Yes — never expose in frontend code |
| Fetch a fresh assertion per session | Yes — never cache or reuse |
| Do not log or store the assertion in the browser | Strongly recommended |
| Update CSP to allow Fruga domains | Yes, if your site uses CSP |
Handle SESSION_EXPIRED event for long sessions | Yes, if users may be active for >15 minutes |