JWT vs Session: który mechanizm uwierzytelniania wybrać?
Uwierzytelnić użytkownika to wiedzieć przy każdym żądaniu, kim jest. Konkurują ze sobą dwie wielkie rodziny: sesje serwerowe, w których serwer śledzi zalogowanego użytkownika, oraz JSON Web Tokens (JWT), w których tożsamość podróżuje w podpisanym tokenie noszonym przez klienta. Wybór wpływa na bezpieczeństwo, zdolność do skalowania i łatwość wylogowania użytkownika. Oto jak zdecydować w zależności od kontekstu.
Sesje serwerowe (stateful)
Przy sesji serwer tworzy identyfikator sesji podczas logowania, przechowuje powiązane dane (tożsamość, role, koszyk) po stronie serwera (pamięć, Redis, baza danych) i odsyła do przeglądarki cookie zawierające tylko ten identyfikator. Przy każdym żądaniu serwer odczytuje cookie, odnajduje sesję i wie, kto mówi.
- Stan po stronie serwera: źródło prawdy pozostaje na serwerze, klient nosi tylko nieprzejrzystą referencję.
- Natychmiastowe unieważnienie: usunięcie sesji po stronie serwera natychmiast wylogowuje użytkownika.
- Cookie: przesyłane automatycznie przez przeglądarkę, najlepiej z
HttpOnly,SecureiSameSite.
JSON Web Tokens (stateless)
JWT to samonośny token złożony z trzech części zakodowanych w base64url i oddzielonych kropkami: nagłówka, payloadu (claimy, na przykład identyfikator użytkownika i wygaśnięcie) oraz podpisu. Serwer podpisuje token podczas logowania; potem wystarczy, że zweryfikuje podpis, aby zaufać treści, niczego nie przechowując.
- Bezstanowy: cała potrzebna informacja znajduje się w tokenie, serwer nie potrzebuje współdzielonej pamięci.
- Weryfikowalny wszędzie: każda usługa znająca klucz może zweryfikować token, co jest praktyczne dla architektur rozproszonych i SSO.
- Narzędzia: możesz sprawdzić token naszym dekoderem JWT, skontrolować jego podpis weryfikatorem JWT lub utworzyć go generatorem JWT.
Tabela porównawcza
| Kryterium | Sesja serwerowa | JWT |
|---|---|---|
| Stan | Stateful (przechowywany na serwerze) | Stateless (noszony przez klienta) |
| Przechowywanie na serwerze | Wymagane (Redis, baza) | Brak |
| Unieważnianie | Natychmiastowe | Trudne przed wygaśnięciem |
| Skalowalność horyzontalna | Wymaga współdzielonego magazynu | Natywna |
| Przesyłany rozmiar | Mały (identyfikator) | Większy (podpisane claimy) |
| Cross-domain / SSO | Ograniczające | Dostosowane |
| Powierzchnia XSS | Niska z cookie HttpOnly | Wysoka przy przechowywaniu w localStorage |
Bezpieczeństwo: XSS, CSRF i unieważnianie
Oba podejścia są bezpieczne, jeśli są dobrze wdrożone, ale ich ryzyka się różnią.
- XSS: cookie sesji
HttpOnlyjest niedostępne dla JavaScriptu, więc chronione przed kradzieżą przez wstrzyknięcie. JWT przechowywany wlocalStoragejest natomiast czytelny dla każdego skryptu, co czyni go atrakcyjnym celem. Przechowywanie JWT w cookieHttpOnlyniweluje tę zaletę JWT, ale ponownie wprowadza ryzyko CSRF. - CSRF: cookies są wysyłane automatycznie, więc podatne na CSRF bez ochrony (atrybut
SameSite, token anty-CSRF). JWT wysyłany ręcznie w nagłówkuAuthorizationnie jest narażony. - Unieważnianie: to słaby punkt JWT. Ponieważ jest samonośny, nie można go unieważnić przed wygaśnięciem bez ponownego wprowadzenia stanu serwera (lista unieważnień, blacklist). Sesję usuwa się natychmiast.
Skalowalność i architektura
Na jednym serwerze sesje są trywialne. Gdy tylko rozłożysz obciążenie na wiele instancji, każda instancja musi mieć dostęp do sesji: potrzebny jest współdzielony magazyn (Redis) lub sticky sessions. JWT błyszczy tutaj, ponieważ każda instancja weryfikuje token bez wywołania sieciowego ani wspólnego magazynu.
- Mikrousługi: JWT propaguje tożsamość z jednej usługi do drugiej bez centralnej bazy.
- Publiczne API i aplikacje mobilne: JWT unika zarządzania cookies po stronie natywnego klienta.
- Klasyczny monolit: sesja pozostaje domyślnie prostsza i bezpieczniejsza.
Kiedy wybrać jedno lub drugie
Wybierz sesje, gdy
- Tworzysz klasyczną aplikację webową z renderowaniem serwerowym
- Natychmiastowe unieważnienie jest krytyczne (bank, ochrona zdrowia, back-office)
- Chcesz domyślnie najbezpieczniejsze rozwiązanie, z najmniejszą liczbą pułapek
- Twoja infrastruktura bez trudu utrzymuje współdzielony magazyn sesji
Wybierz JWT, gdy
- Udostępniasz API używane przez SPA, aplikacje mobilne lub strony trzecie
- Masz architekturę mikrousług lub SSO między domenami
- Musisz skalować horyzontalnie bez współdzielonego magazynu
- Akceptujesz zarządzanie krótkim wygaśnięciem i odświeżaniem tokenów
Zalecenie
Dla większości aplikacji webowych sesje serwerowe pozostają najbezpieczniejszym i najprostszym wyborem: natychmiastowe unieważnianie, cookie HttpOnly i zero zarządzania tokenem po stronie klienta. Zarezerwuj JWT dla przypadków, w których jego bezstanowość przynosi realną wartość: bezstanowe API, aplikacje mobilne, mikrousługi, SSO.
Jeśli wybierasz JWT, zachowaj krótki czas życia (kilka minut) w połączeniu z refresh tokenem przechowywanym w cookie HttpOnly i przewidź listę unieważnień dla przypadków wrażliwych. Łączysz wtedy to, co najlepsze z obu światów.
Najczęściej zadawane pytania
Czy JWT jest szyfrowany?
Nie, domyślnie JWT jest jedynie podpisany, nie szyfrowany. Jego payload jest zakodowany w base64url i czytelny dla każdego, kto go przechwyci. Nigdy nie umieszczaj wrażliwych danych w postaci jawnej w JWT. Aby zaszyfrować treść, należy użyć JWE (JSON Web Encryption).
Gdzie przechowywać JWT po stronie klienta?
Najbezpieczniejsze jest cookie HttpOnly, Secure i SameSite, które chroni przed kradzieżą przez XSS. localStorage jest prostszy, ale wystawia token na każdy złośliwy skrypt. Unikaj go w przypadku tokenów o wysokich uprawnieniach.
Jak wylogować użytkownika z JWT?
Ponieważ token jest samonośny, rzeczywiste wylogowanie wymaga albo poczekania na jego wygaśnięcie, albo prowadzenia listy unieważnień po stronie serwera. Dlatego stosuje się krótkie czasy życia i refresh token, który można unieważnić.
Czy można łączyć sesje i JWT?
Tak, to powszechna praktyka: access token JWT o krótkim czasie życia do wywołań API oraz refresh token zarządzany jak sesja (przechowywany i unieważnialny po stronie serwera) do odnawiania access tokena.