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.

💡
This page covers frontend-specific security. For the full authentication model including backend JWT signing and HMAC request signing, see the Authentication guide.

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.

TokenCreated byPasses through browser?LifetimeCan host page access it?
Assertion JWTYour backendYes — 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 tokenFrugaNo — exchanged and stored entirely within the widget iframe15 minutes, auto-refreshedNo — 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;
DirectiveDomainWhy it is needed
frame-srchttps://widget.fruga.comAllows the widget iframe to load
script-srchttps://cdn.fruga.comAllows the SDK script tag to load
connect-srchttps://api.fruga.comAllows the SDK to make API calls to Fruga (from within the iframe context)
⚠️
If you have a strict CSP and the widget is not loading, open your browser console — a blocked resource will appear as a CSP violation error and will identify exactly which directive needs to be updated.

Summary

PracticeRequired?
Keep Signing Secret server-side onlyYes — never expose in frontend code
Fetch a fresh assertion per sessionYes — never cache or reuse
Do not log or store the assertion in the browserStrongly recommended
Update CSP to allow Fruga domainsYes, if your site uses CSP
Handle SESSION_EXPIRED event for long sessionsYes, if users may be active for >15 minutes