Gotovo svaki Angular tutorial spremi JWT u localStorage i krene dalje. Radi, jednostavno je i — svakom skriptu koji se ikad izvrši na vašoj domeni daje token na pladnju. Na sustavu gdje curenje identiteta nije bilo prihvatljivo morali smo to riješiti drugačije.
Zašto localStorage nije trezor
localStorage je čitljiv iz bilo kojeg JavaScripta na istoj domeni. Jedan XSS propust — u vašem kodu, u ovisnosti, u marketinškom skriptu koji je netko dodao preko admin sučelja — i napadač ima token i sve što token otvara. Isto vrijedi za sessionStorage i za obični kolačić bez httpOnly zastavice.
Obrazac: token u memoriji, osvježavanje u kolačiću
Pristupni token živi isključivo u memoriji — u polju servisa za autentikaciju, nigdje na disku. Interceptor ga dodaje na svaki zahtjev. Za dugotrajnu sesiju server izdaje refresh token kao httpOnly, Secure, SameSite kolačić: preglednik ga šalje sam, a JavaScript ga ne može ni pročitati ni ukrasti.
Kad korisnik osvježi stranicu, memorija je prazna. Aplikacija pri pokretanju pozove endpoint za osvježavanje; kolačić putuje sam, server vrati novi pristupni token i korisnik ostaje prijavljen a da ništa nije primijetio. Token s kratkim rokom trajanja ograničava štetu ako ipak procuri.
Što se mijenja u aplikaciji
Tri stvari traže pažnju. Prvo, početno učitavanje ima jedan poziv više, pa guard rute mora pričekati rezultat umjesto da korisnika odmah šalje na prijavu. Drugo, interceptor na 401 pokušava osvježavanje jednom i ponavlja izvorni zahtjev — a ne ulazi u petlju. Treće, odjava mora poništiti kolačić na serveru, jer ga klijent ne može obrisati.
Cijena i kad se isplati
Obrazac nije besplatan: server mora voditi refresh tokene i moći ih opozvati, a svaka kartica preglednika osvježava sesiju za sebe. Za interni alat s tri korisnika možda je pretjerano. Za sve što drži tuđe osobne podatke — nije pitanje isplati li se, nego koliko ste spremni objašnjavati zašto niste.
Token koji JavaScript ne može pročitati ne može ni ukrasti — sve ostalo je nada.