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, Secure i SameSite.

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
StanStateful (przechowywany na serwerze)Stateless (noszony przez klienta)
Przechowywanie na serwerzeWymagane (Redis, baza)Brak
UnieważnianieNatychmiastoweTrudne przed wygaśnięciem
Skalowalność horyzontalnaWymaga współdzielonego magazynuNatywna
Przesyłany rozmiarMały (identyfikator)Większy (podpisane claimy)
Cross-domain / SSOOgraniczająceDostosowane
Powierzchnia XSSNiska z cookie HttpOnlyWysoka 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 HttpOnly jest niedostępne dla JavaScriptu, więc chronione przed kradzieżą przez wstrzyknięcie. JWT przechowywany w localStorage jest natomiast czytelny dla każdego skryptu, co czyni go atrakcyjnym celem. Przechowywanie JWT w cookie HttpOnly niweluje 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łówku Authorization nie 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.