JWT (JSON Web Token) — to kompaktowy format przesyłania danych między stronami w postaci obiektu JSON zabezpieczonego podpisem cyfrowym. Token może być podpisany za pomocą HMAC (klucz symetryczny) lub RSA/ECDSA (para asymetryczna), co gwarantuje integralność i autentyczność danych. Według IETF RFC 7519, 2015, JWT jest używany w milionach aplikacji do uwierzytelniania, bezpiecznej wymiany claims i jako format ID Token w OpenID Connect.
Najważniejsze
JSON Web Token (JWT) — to otwarty standard (RFC 7519) definiujący kompaktowy i samowystarczalny sposób przesyłania informacji między stronami w postaci obiektu JSON. Informacje w JWT nazywane są claims — twierdzeniami o subiekcie (użytkowniku) i dodatkowych atrybutach. Każdy claim to para klucz-wartość: identyfikator użytkownika, rola, czas wygaśnięcia, wystawca.
JWT nazywany jest samowystarczalnym, ponieważ wszystkie informacje potrzebne do weryfikacji znajdują się wewnątrz samego tokena. Serwer nie musi odwoływać się do bazy danych ani zewnętrznego magazynu, aby upewnić się o ważności tokena — wystarczy sprawdzić podpis. Ta właściwość czyni JWT idealnym dla systemów rozproszonych i architektury mikrousług, gdzie kilka usług musi uwierzytelniać żądania bez wspólnego magazynu sesji.
Według danych Auth0, 2025, ponad 65% aplikacji mobilnych i internetowych używa JWT jako głównego formatu tokena do uwierzytelniania API, wyprzedzając losowe ciągi znaków (opaque tokens) i identyfikatory sesji.
JWT składa się z trzech części oddzielonych kropkami: header.payload.signature. Każda część to zakodowany w Base64url JSON. Przyjrzyjmy się każdej części szczegółowo.
Header zawiera dwa obowiązkowe pola: alg (algorithm — algorytm podpisu) i typ (type — typ tokena, zawsze „JWT”). Algorytm może być symetryczny (HS256 — HMAC z SHA-256) lub asymetryczny (RS256 — RSA z SHA-256, ES256 — ECDSA z P-256). Algorytmy asymetryczne są preferowane, ponieważ umożliwiają klientowi weryfikację podpisu bez posiadania tajnego klucza.
Przykład zdekodowanego header:
{
"alg": "RS256",
"typ": "JWT",
"kid": "key-id-1"
}
Payload zawiera claims — twierdzenia o subiekcie. Claims dzielą się na trzy typy: zarejestrowane (iss, sub, aud, exp, nbf, iat, jti), publiczne (definiowane przez programistę w IANA Registry) i prywatne (uzgodnione między stronami). sub (subject) — unikalny identyfikator użytkownika. exp (expiration) — timestamp wygaśnięcia tokena. iss (issuer) — wystawca tokena.
{
"sub": "user-abc-123",
"iss": "https://auth.example.com",
"aud": "my-mobile-app",
"exp": 1812345678,
"iat": 1812342078,
"role": "premium_user"
}
Signature jest tworzona poprzez zastosowanie algorytmu podpisu do konkatenacji header i payload z użyciem tajnego lub prywatnego klucza. Wzór: HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret) dla HMAC, lub RSASHA256(...) dla algorytmu asymetrycznego. Odbiorca oblicza podpis w ten sam sposób i porównuje z otrzymanym — jeśli są zgodne, dane nie zostały zmienione.
Proces działania JWT składa się z dwóch faz: tworzenia (wystawienia) tokena przez serwer uwierzytelniania i weryfikacji tokena przez klienta lub serwer zasobów. Serwer uwierzytelniania otrzymuje dane uwierzytelniające użytkownika, tworzy payload z claims i podpisuje go. Otrzymany JWT jest wysyłany do klienta w odpowiedzi na zapytanie logowania lub w treści odpowiedzi OAuth 2.0 / OpenID Connect.
W aplikacjach mobilnych JWT jest używany w następujący sposób: po pomyślnym zalogowaniu użytkownik otrzymuje access token w formacie JWT. Aplikacja przechowuje go w bezpiecznym magazynie (Keychain na iOS, EncryptedSharedPreferences na Androidzie). Przy każdym zapytaniu do API aplikacja dodaje nagłówek Authorization: Bearer <token>. Serwer API sprawdza podpis JWT, wyodrębnia claims i na ich podstawie podejmuje decyzję o dostępie — bez odwoływania się do bazy danych.
Według danych Google Codelabs, 2025, użycie JWT w Firebase Authentication zmniejsza liczbę zapytań do serwera uwierzytelniania o 40–60% w porównaniu z tokenami sesyjnymi, ponieważ dane są weryfikowane lokalnie na każdej mikrousłudze. Jest to szczególnie ważne w architekturach o wysokim obciążeniu, gdzie każda milisekunda opóźnienia wpływa na doświadczenie użytkownika. Przy 50 000 zapytań na minutę przejście na JWT może zaoszczędzić do 10 instancji serwerowych obsługujących zapytania introspection.
JWT i Session Token rozwiązują to samo zadanie — uwierzytelnianie zapytań — ale zasadniczo różnią się architekturą. Session Token to losowy ciąg identyfikatorów odnoszący się do danych sesji przechowywanych na serwerze (stateful). JWT — samowystarczalny token zawierający wszystkie dane w sobie (stateless).
| Parametr | JWT | Session Token |
|---|---|---|
| Przechowywanie danych | Wewnątrz tokena (samowystarczalny) | Na serwerze (magazyn sesji) |
| Skalowanie | Nie wymaga współdzielonego magazynu | Wymaga Redis/BD dla wielu serwerów |
| Unieważnienie tokena | Skomplikowane (potrzebna czarna lista) | Proste (usunięcie sesji z BD) |
| Rozmiar | Duży (500–2000 bajtów) | Mały (16–64 bajty) |
| Weryfikacja podpisu | Kryptograficzna | Brak (porównanie ciągów) |
JWT sprawdza się w systemach rozproszonych: mikrousługi mogą weryfikować token lokalnie bez wspólnego magazynu. Na przykład w architekturze z pięcioma mikrousługami każda usługa weryfikuje JWT w 1–2 ms bez wywołania sieciowego, podczas gdy session token wymaga odwołania do scentralizowanego Redis przy każdym zapytaniu, dodając 10–30 ms opóźnienia. Jednakże JWT jest trudny do unieważnienia — jeśli token został już wystawiony, jest ważny do czasu wygaśnięcia. Session Token łatwo unieważnić, usuwając wpis z BD lub Redis.
Dla aplikacji mobilnych podejście kombinowane — JWT z krótkim czasem życia (15–30 minut) i Refresh Token — zapewnia równowagę między wydajnością a bezpieczeństwem. JWT jest używany do dostępu do API, a refresh token (zazwyczaj opaque) do uzyskiwania nowych JWT. W przypadku kompromitacji JWT atakujący ma dostęp na 15–30 minut; w przypadku kompromitacji refresh token sesja jest blokowana poprzez rotation i wykrywanie ponownego użycia.
Bezpieczeństwo JWT zależy od prawidłowej implementacji. Najczęstszą podatnością jest atak „alg none”: atakujący zmienia header tokena na „alg”: „none”, a serwer, nie sprawdziwszy algorytmu, akceptuje fałszywy token. Ochrona: zawsze sprawdzać, czy algorytm w header odpowiada oczekiwanemu (RS256, ES256) i odrzucać tokeny z alg: none.
Podatności JWT obejmują również: słaby tajny klucz dla HMAC (złamanie w ciągu minut), wyciek klucza prywatnego (podpisywanie dowolnych danych w imieniu serwera), przechowywanie poufnych danych w payload (JWT nie szyfruje, tylko podpisuje), atak przez JWK header injection (wstrzyknięcie własnego klucza publicznego). Użycie sprawdzonych bibliotek — Nimbus JOSE + JWT, jjwt (io.jsonwebtoken), PyJWT — zmniejsza ryzyko wykorzystania tych podatności.
Dodatkowym środkiem ochrony jest JWK Thumbprint (RFC 7638): powiązanie klucza publicznego z tokenem poprzez odcisk (thumbprint) w header. Jeżeli serwer przechowuje oczekiwany thumbprint dla każdego klienta, atak JWK header injection staje się niemożliwy — serwer odrzuca każdy klucz niezgodny z zarejestrowanym. OAuth Security Workshop 2025 zaleca JWK Thumbprint jako obowiązkową ochronę dla wszystkich JWT używanych w aplikacjach finansowych i medycznych.
Biblioteka jjwt (auth0/java-jwt) umożliwia tworzenie i weryfikację JWT w aplikacji Android w kilku liniach. W poniższym przykładzie serwer generuje token z sub i role, a klient sprawdza podpis. Do bezpiecznego przechowywania tajnego klucza na serwerze używaj zmiennych środowiskowych lub HSM (Hardware Security Module) — przechowywanie klucza w kodzie lub pliku konfiguracyjnym jest poważnym błędem bezpieczeństwa.
val secret = "my-256-bit-secret-key-here"
val token = JWT.create()
.withSubject("user-abc-123")
.withIssuer("auth.example.com")
.withClaim("role", "premium_user")
.withExpiresAt(Date(System.currentTimeMillis() + 3600000))
.sign(Algorithm.HMAC256(secret))
// Wysyłanie tokena do klienta
println("JWT: $token")
fun verifyToken(token: String): Boolean {
return try {
val decoded = JWT.require(Algorithm.HMAC256(secret))
.withIssuer("auth.example.com")
.build()
.verify(token)
// Podpis jest poprawny, claims wyodrębnione
println("Subject: ${decoded.subject}")
true
} catch (e: Exception) {
println("Token invalid: ${e.message}")
false
}
}
Często zadawane pytania
Nie. JWT jest podpisywany, a nie szyfrowany — każdy może zdekodować Base64 payload i odczytać dane. Poufne informacje (hasła, numery kart, dane osobowe) powinny być przesyłane tylko w formie zaszyfrowanej przez JWE (JSON Web Encryption).
Zalecany jest ES256 (ECDSA z P-256) — zapewnia równoważny poziom bezpieczeństwa RSA 2048-bit przy znacznie mniejszym rozmiarze podpisu. Dla zgodności z systemami starszymi odpowiedni jest RS256. HS256 (HMAC) wymaga bezpiecznej wymiany tajnego klucza, co jest trudniejsze w architekturze rozproszonej.
JWT nie można unieważnić bezpośrednio — jest ważny do exp. Rozwiązania: używać krótkiego czasu życia (15–30 minut), prowadzić czarną listę unieważnionych jti (JWT ID) na serwerze lub powiązać tokeny z wersją tajnego klucza. Refresh token przy tym jest unieważniany standardowo — przez usunięcie z magazynu.
Bearer token — to koncepcja: każdy token, którego posiadacz (bearer) może użyć do dostępu. JWT — to konkretny format tokena. Bearer token może być JWT lub opaque string. JWT dodaje do koncepcji Bearer samowystarczalność i weryfikację kryptograficzną.
Typowy JWT z podpisem RS256 zajmuje 500–2000 bajtów. Jeśli payload zawiera wiele niestandardowych claims lub jest używany asymetryczny podpis z dużym kluczem, rozmiar może osiągnąć 4–5 KB. To znacznie więcej niż session token (16–64 bajty), co wpływa na rozmiar nagłówków HTTP.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również