Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Don't store auth tokens in localStorage/sessionStorage; use HttpOnly cookies
Browser-accessible storage (localStorage/sessionStorage/JS memory) for session or refresh tokens is XSS-exfiltratable; keep them in HttpOnly cookies.
Bad example
| 1 | // React login handler |
| 2 | const { token } = await api.login(email, password); |
| 3 | localStorage.setItem('accessToken', token); // readable by any script on the page |
| 4 |
|
| 5 | // later, on every request |
| 6 | fetch('/api/me', { |
| 7 | headers: { Authorization: `Bearer ${localStorage.getItem('accessToken')}` }, |
| 8 | }); |
Explanation (EN)
localStorage is fully readable by JavaScript, so a single XSS payload or a compromised npm dependency can exfiltrate the token and impersonate the user from anywhere. Persisting a long-lived bearer token in web storage turns any script execution on the origin into full account takeover.
Objašnjenje (HR)
localStorage je u potpunosti citljiv iz JavaScripta pa jedan XSS payload ili kompromitirana npm ovisnost moze izvuci token i predstavljati se kao korisnik s bilo kojeg mjesta. Trajno cuvanje dugotrajnog bearer tokena u web pohrani pretvara svako izvrsavanje skripte na originu u potpuno preuzimanje racuna.
Good example
| 1 | // Server sets the session in an HttpOnly cookie (Next.js Route Handler) |
| 2 | import { cookies } from 'next/headers'; |
| 3 |
|
| 4 | export async function POST(req: Request) { |
| 5 | const { email, password } = await req.json(); |
| 6 | const session = await login(email, password); |
| 7 | cookies().set('session', session.id, { |
| 8 | httpOnly: true, secure: true, sameSite: 'lax', path: '/', |
| 9 | }); |
| 10 | return Response.json({ ok: true }); |
| 11 | } |
| 12 |
|
| 13 | // Client just relies on the cookie being sent automatically |
| 14 | await fetch('/api/me', { credentials: 'same-origin' }); // no token in JS at all |
Explanation (EN)
Holding the session in an HttpOnly cookie means JavaScript — and therefore XSS — can never read it, while the browser still attaches it to same-origin requests. The client never handles the raw token, so there is no bearer credential to exfiltrate from web storage or memory.
Objašnjenje (HR)
Drzanje sesije u HttpOnly kolacicu znaci da je JavaScript — a time i XSS — nikad ne moze procitati, dok ga preglednik i dalje prilaze same-origin zahtjevima. Klijent nikad ne barata sirovim tokenom pa nema bearer vjerodajnice koja bi se izvukla iz web pohrane ili memorije.
Notes (EN)
This is the frontend counterpart to the cookie-attributes rule; together they keep the session out of JS reach end to end.
Bilješke (HR)
Ovo je frontend parnjak pravilu o atributima kolacica; zajedno drze sesiju izvan dosega JS-a od kraja do kraja.
Exceptions / Tradeoffs (EN)
If your architecture genuinely requires bearer tokens in JS (e.g. a cross-origin API you don't control), keep the access token short-lived and in memory only (not localStorage), keep the refresh token in an HttpOnly cookie, and rely on a strict CSP to reduce XSS reach. Cookie-based sessions still require CSRF protection (SameSite + token).
Iznimke / Tradeoffi (HR)
Ako arhitektura stvarno zahtijeva bearer tokene u JS-u (npr. cross-origin API koji ne kontroliras), drzi access token kratkotrajnim i samo u memoriji (ne u localStorage), refresh token u HttpOnly kolacicu, i oslanjaj se na strogi CSP da smanjis doseg XSS-a. Sesije temeljene na kolacicima i dalje trebaju CSRF zastitu (SameSite + token).