JWT (JSON Web Token) to string z trzech części w base64url, header.payload.signature, który serwer daje użytkownikowi przy logowaniu i sprawdza przy każdym kolejnym żądaniu. Podpis dowodzi, że token wydał serwer i nikt go nie zmienił, ale payload jest tylko zakodowany, a nie zaszyfrowany, więc każdy może go odczytać. W Node.js tworzysz go przez new SignJWT(payload).sign(secret) z pakietu jose i sprawdzasz przez jwtVerify(token, secret).
Wypisuje 3 parts, a potem user-42 admin: jwtVerify sprawdził podpis i wygaśnięcie i zwrócił payload. Zmień o jeden znak sekret przekazany do jwtVerify i uruchom ponownie: weryfikacja kończy się błędem signature verification failed.
Co jest w środku JWT
Header i payload to JSON zakodowany w base64url. Dekodowanie nie wymaga klucza, dlatego JWT nigdy nie może zawierać hasła ani niczego innego tajnego.
Pierwsze dwie linie wypisują {"alg":"HS256"} i {"name":"Ada","role":"editor"}, czytelne dla każdego, kto ma token. Podpis (43 znaki dla HS256) sprawia, że nie da się go zmienić: zamień editor na admin, a podpis przestanie pasować.
| Część | Zawiera | Tajne? |
|---|---|---|
| Header | Algorytm, np. HS256 | Nie, czytelne |
| Payload | Claimy: sub (id użytkownika), exp (wygaśnięcie), iat (czas wydania), twoje własne pola | Nie, czytelne |
| Podpis | Podpis HMAC lub RSA dwóch pierwszych części | Do utworzenia potrzebny jest klucz |
Wygaśnięcie i nieudana weryfikacja
setExpirationTime('15m') zapisuje claim exp, a jwtVerify odrzuca token po tym czasie. Każde odrzucenie to błąd z code, więc odróżnisz wygasły token od podrobionego.
Pierwsze sprawdzenie kończy się błędem ERR_JWT_EXPIRED, a drugie, z podmienionym podpisem, błędem ERR_JWS_SIGNATURE_VERIFICATION_FAILED. Dla twojego API oba znaczą to samo (odpowiedz 401), ale logowanie kodu pomaga przy debugowaniu.
Ochrona trasy Express tokenem Bearer
Klient loguje się raz, zachowuje token i wysyła go w nagłówku Authorization jako Bearer <token>. Middleware go weryfikuje i umieszcza payload w req.user dla trasy.
Pierwsze żądanie, GET /me bez tokena, dostaje 401 i Missing token. Wyślij POST /login z body {"username": "ada", "password": "lovelace"}, skopiuj token z odpowiedzi, a potem wyślij jeszcze raz GET /me z nagłówkiem Authorization: Bearer <token>: odpowiedź to {"id":"user-1","name":"ada"}.
Pakiet jsonwebtoken
jsonwebtoken (npm install jsonwebtoken) to drugi popularny wybór. Jego sign i verify są synchroniczne (z opcjonalnymi callbackami), zamiast zwracać promise:
const jwt = require('jsonwebtoken');
const token = jwt.sign({ role: 'admin' }, process.env.JWT_SECRET, {
subject: 'user-42',
expiresIn: '1h',
});
const payload = jwt.verify(token, process.env.JWT_SECRET); // throws if invalid
W nowym kodzie używaj jose: nie ma zależności i działa też w środowiskach edge i przeglądarkach. Tokeny z obu bibliotek to standardowe JWT, więc jedna zweryfikuje to, co podpisała druga.
Częste błędy
Używanie decode zamiast verify. jwt.decode i decodeJwt czytają payload bez sprawdzania podpisu, więc podrobiony token przejdzie. Ufaj tylko temu, co zwraca verify.
Krótki albo zacommitowany sekret. Sekret HS256, który da się zgadnąć, pozwala atakującym podpisywać własne tokeny. Używaj co najmniej 32 losowych bajtów, czytaj je z process.env i nigdy ich nie commituj.
Tokeny, które nigdy nie wygasają. Bez exp wykradziony token działa w nieskończoność. Zawsze ustawiaj wygaśnięcie.
Najczęściej zadawane pytania
Gdzie klient powinien przechowywać JWT?
W aplikacji przeglądarkowej najbezpieczniejsze jest ciasteczko HttpOnly, Secure, bo skrypty strony nie mogą go odczytać. localStorage jest prostszy, ale każdy skrypt na stronie, także wstrzyknięty, może odczytać token. Aplikacje mobilne używają bezpiecznego magazynu platformy.
Jak wylogować użytkownika przy JWT?
Serwer nie może unieważnić tokena, który już podpisał; token jest ważny do wygaśnięcia. Usuń go po stronie klienta, trzymaj krótki czas życia access tokenów, a jeśli potrzebujesz natychmiastowego wylogowania, prowadź po stronie serwera listę unieważnionych identyfikatorów tokenów (jti) i sprawdzaj ją przy każdym żądaniu.
HS256 czy RS256?
HS256 używa jednego wspólnego sekretu do podpisywania i weryfikacji, co pasuje do jednego backendu. RS256 podpisuje kluczem prywatnym i weryfikuje publicznym, więc inne serwisy mogą sprawdzać tokeny bez możliwości ich tworzenia.
Jak długo powinien być ważny JWT?
Access tokeny zwykle są ważne od 5 do 60 minut. Przy dłuższych sesjach łącz je z ostrożniej przechowywanym refresh tokenem, który klient wymienia na nowy access token.