IntegrAuth Academy
Identity, security and practical AI — free, from zero. Byte-sized lessons, real diagrams, hands-on labs, zero fluff. Two programs, each with its own exam and certificate.
English is hard? Your browser can translate this page into Tamil, Telugu, Kannada, Malayalam or Hindi. Tech words stay in English so you learn the real names.
How?
- Chrome on phone: ⋮ menu → Translate
- Chrome on computer: right-click → Translate to …
- Edge: the translate icon in the address bar
Where tokens live — SPAs and the BFF
Maya is a parent. She opens her son's school-fees web app and logs in. The app now holds two tokens and must keep them somewhere. Your goal: choose a safe place for tokens in a browser app.
- Whoever holds a token can act as Maya, and pay her bills.
- Any script on the page can read localStorage, so XSS steals tokens.
- A cookie can hide from scripts, but the browser sends it by itself (CSRF).
- A BFF keeps the tokens on a server. The browser holds only a session cookie.
The app that runs in a tab
The fees app is a single-page app (SPA). It downloads once and then runs inside Maya's browser tab. It has no server of its own, so it is a public client: it cannot keep a secret. It logs in with the authorization-code flow and PKCE (Proof Key for Code Exchange). (See the birth of a token.) The login ends with an access token and often a refresh token.
Both are bearer tokens. Whoever holds one is Maya. Like cash, it does not care who carries it. The app must reach the tokens. An attacker's script must not.
Option A: localStorage
localStorage is a small box where the browser saves values for a site. It is easy to use. But any script on the page can read it too. That is the whole problem.
Cross-site scripting (XSS) means an attacker runs their JavaScript inside your page. Say the fees app loads a chat-help widget from another company, and the widget has a bug. An attacker's script now runs on every parent's page. It reads localStorage and sends the tokens away. (More in session hijacking.)
Option B: a cookie
A cookie can be locked in ways localStorage cannot:
- HttpOnly: JavaScript cannot read the cookie.
- Secure: the cookie travels only over HTTPS.
- SameSite: the browser does not send the cookie on requests that another site starts.
But there is a catch. If JavaScript cannot read the cookie, your app cannot copy the token into an Authorization header. So the browser sends the cookie by itself. That makes CSRF (cross-site request forgery) possible. Say Maya opens a bad page in another tab. The page tricks her browser into sending a "pay" request to the fees API. The browser adds her cookie. SameSite is the main brake.
So a cookie is safer to store but harder to use.
Option C: the BFF
The backend-for-frontend (BFF) pattern removes the problem. You run a small server for your app, and the browser talks only to it. The BFF finishes the login and keeps the access and refresh tokens on the server. The browser gets one thing: a hardened session cookie (HttpOnly, Secure, SameSite).
When Maya's page needs data, it calls the BFF with that cookie. The BFF adds her tokens and calls the real API for her. The browser never holds an access token, so XSS has none to steal. Injected script can still send requests through her open tab. So XSS is limited, not harmless.
a hotel. With localStorage, your room key hangs on the lobby wall for anyone to take. With a BFF, the front desk keeps the real keys. You carry only a numbered wristband (the session cookie), and the desk can cancel it.
A BFF costs you a server to run and patch. In return, XSS cannot steal tokens, and refresh tokens sit where rotation and revocation are easy. If tokens must stay in the browser, keep access tokens very short. Never put a refresh token in localStorage.
Go deeper: what the standard says (RFC 10017)
In August 2026 the IETF published RFC 10017, "OAuth 2.0 for Browser-Based Applications", as a Best Current Practice. It lists three patterns, from most secure to least secure.
- A BFF keeps tokens on the server and proxies API calls. The RFC "strongly recommends" it for business and sensitive apps.
- A token-mediating backend is a server that acts as a confidential client and runs the OAuth flow. But the browser still receives and uses the access token. Use it only if a full BFF is not possible.
- A browser-only client does everything itself. The RFC says this pattern is open to all the attacks it describes.
Keeping tokens only in JavaScript memory is better than localStorage. But a script injected into the same page can still reach them. Treat it as a weaker fallback, not a fix.
Go deeper: cookie details that matter
Add the __Host- prefix to the session cookie name. The browser then accepts the cookie only if it is Secure, has Path=/ and has no Domain. So a sibling subdomain cannot overwrite it.
SameSite=Strict is the strongest value. Browsers treat a cookie with no SameSite value as Lax. Still, write the value yourself. A draft that will replace RFC 6265 is rewriting the cookie rules. As of October 2026, it is with the RFC Editor.
🧪 Interactive lab — enable JavaScript to play with this one.
Grade whether your own session cookie sets HttpOnly, Secure and SameSite with Cookie Check. See what a leaked bearer token exposes with ID Token Check.