JSON Web Token (JWT)

JSON Web Token (dalej JWT) jest standardem ułatwiającym bezpieczną wymianę informacji między systemami.
W szczególności może być pomocny w procesach uwierzytelniania i autoryzacji użytkowników w aplikacji WWW.

Twórcą jest organizacja IETF, która w 2015 roku opublikowała jego propozycję w RFC 7519.

Struktura

JWT jest ciągiem znaków, który powstaje w wyniku połączenia trzech pakietów informacji oddzielonych kropkami: dwóch zakodowanych z pomocą Base64Url oraz jednego zakodowanego dowolnym mechanizmem (np. HMAC SHA-256).

Bardzo ważne jest aby używać właśnie Base64Url, a nie „zwykłego” Base64, bo musimy zapewnić, że JWT będzie można przesłać również jako parametr w URL.

Opisowo, token można przedstawić następująco:

nagłówek.ładunek.sygnatura

Nagłówek

Jest to pakiet meta danych JWT. Zwykle niesie on dwie informacje:

„typ”
(Type) Typ. Pole określające typ tokenu. Domyślnie wartość to „JWT”

„alg”
(Algorithm) Algorytm. Mechanizm kodowania podpisu.
W przykładzie poniżej „HS256” dla HMAC SHA-256

{
  "typ": "JWT",
  "alg": "HS256"
}

Ten pakiet danych w formie Base64Url wygląda następująco:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

Ładunek

Tu umieszczone są dane, które chcemy przesłać między systemami.
Nie licząc kilku zastrzeżonych nazw, o których piszę niżej, model danych jest dowolny, ale musi być zgodny ze standardem JSON.

Przykładowo możemy przyjąć, że chcemy przesłać identyfikator użytkownika, jego imię i nazwisko, oraz informację, czy ma uprawnienia administratora.

{
  "id": 1,
  "name": "John Doe",
  "isAdmin": true
}

Połączywszy nagłówek z ładunkiem otrzymamy:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6IkpvaG4gRG9lIiwiaXNBZG1pbiI6dHJ1ZX0

Nazwy zastrzeżone

Propozycja standardu RFC 7519 definiuje mały zbiór zastrzeżonych nazw, które – jeżeli już zostaną użyte w JWT – muszą mieć wartości zgodne ze standardem.

Użycie wszystkich zastrzeżonych nazw jest opcjonalne.

„iss”
(Issuer) Wydawca. Pole to identyfikuje zleceniodawcę stworzenia tokenu. Sposób przetwarzania (wyciągania wniosków) z wartości tego pola pozostaje w gestii aplikacji.
Wymagane jest rozróżnianie wielkości liter. Wartość może być dowolnym ciągiem znaków. Jeżeli ciąg znaków zawiera znak dwukropka „:”, musi być zgodny ze standardem URI (RFC 3986).

„sub”
(Subject) Podmiot/Obiekt. Wartość pola wskazuje obiekt, którego dotyczą dane przekazywane w ładunku. Dobrym pomysłem jest przekazywanie tu identyfikatora użytkownika podczas autoryzacji. Czyli w powyższych przykładach zastąpił by pole „id”.
Wartość powinna być unikalna w kontekście wydawcy lub w kontekście globalnym.
Sposób przetwarzania wartości leży w gestii aplikacji.
Wymagane jest rozróżnianie wielkości liter. Wartość może być dowolnym ciągiem znaków. Jeżeli ciąg znaków zawiera dwukropek „:”, musi być zgodny ze standardem URI.

„aud”
(Audience) Publiczność. Określa odbiorców, dla których jest przeznaczona treść niesiona w JWT.
Aplikacja korzystająca z tego pola powinna rozróżniać grupy odbiorców i pozwalać lub nie pozwalać na dostęp do zawartości JWT na tej podstawie.
Od aplikacji zależą konkretne możliwe wartości tego pola.
Wymagane jest rozróżnianie wielkości liter. Wartość może być dowolnym ciągiem znaków. Jeżeli ciąg znaków zawiera dwukropek „:”, musi być zgodny ze standardem URI.

„exp”
(Expiration Time) Czas ważności/wygaśnięcia. Zawiera informację o terminie wygaśnięcia tokenu, co pozwala na przykład na przerwanie sesji dostępu do danych dla użytkowników.
Wartość musi być jednoznacznie typu timestamp (numeryczna ilość sekund od daty 1970-01-01T00:00:00Z UTC).

„nbf”
(Not Before) Nie przed. Zawiera informację o minimalnym czasie, od kiedy można przetwarzać ten token.
Próba przetwarzania danych przed podanym czasem musi być odrzucona.
Wartość musi być jednoznacznie typu timestamp (numeryczna ilość sekund od daty 1970-01-01T00:00:00Z UTC).

„iat”
(Issued At) Wydane o (czasie). Pole to przechowuje informację o czasie utworzenia tokenu.
Wartość musi być jednoznacznie typu timestamp (numeryczna ilość sekund od daty 1970-01-01T00:00:00Z UTC).

„jti”
(JWT ID) Identyfikator JWT.
Implementacja musi zapewniać, że wartość identyfikatora się nie powtórzy.
Pole to może posłużyć do weryfikacji i zabezpieczenia przed powtórnym przetworzeniem tego samego tokenu.
Wartość pola może być dowolny ciągiem znaków.

Publiczne nazwy pól

Publiczne nazwy spoza zbioru zastrzeżonych, żeby nie stanowiły zagrożenia konfliktów w komunikacji między systemami, powinny być zarejestrowane w IANA, w ramach rejestru JSON Web Token. Warto zajrzeć do rejestru przed zdefiniowaniem listy pól we własnej implementacji jest duża szansa, że pole, którego potrzebujemy jest już zdefiniowane publicznie.
Jeżeli nie ma możliwości ich rejestracji, powinny mieć formę która zapewni odporność na kolizję. Np. być zgodne z URI.

Prywatne nazwy pól

Prywatnie mamy właściwie dowolność w nazewnictwie pól. Jednak należy pamiętać, że ich stosowanie jest potencjalnym powodem kolizji w komunikacji między systemami.
Powyższy przykład pokazuje właśnie taką dowolność w nazewnictwie pól, powinny być więc stosowane tylko i wyłącznie w naszej aplikacji.
Jeżeli decydujemy się na budowę wielu aplikacji lub implementujemy komunikację z aplikacjami innych producentów, należy poważnie rozważyć użycie zastrzeżonych i publicznych nazw pól.

Sygnatura

Służy potwierdzeniu autentyczności JWT.
Jest ciągiem znaków powstałym przez zaszyfrowanie nagłówka, ładunku oraz opcjonalnego sekretnego klucza, który może „być znany” przez serwer, do którego wysyłamy dane.

SHA256(eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6IkpvaG4gRG9lIiwiaXNBZG1pbiI6dHJ1ZX0.secret_key)

Dokładając powyższą sygnaturę do naszego pakietu danych otrzymamy:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6IkpvaG4gRG9lIiwiaXNBZG1pbiI6dHJ1ZX0.GWouVL5CFXDvxUql-aOcAzvuoOCpopHaXW9M-nS2wV8

Zastosowanie

JWT może być używany jako medium przenoszące dowolne informacje między systemami. Najbardziej popularnym jest weryfikacja, czy użytkownik ma prawo dostępu do danych w systemie.

Przykładowy scenariusz komunikacji systemów w celu weryfikacji użytkownika może wyglądać następująco:

  1. Użytkownik otwiera formularz logowania w dowolnej aplikacji przeglądarkowej, czy mobilnej. Wpisuje swoje dane dostępowe. Aplikacja wysyła te dane do serwera z zaimplementowaną obsługą JWT.
  2. Aplikacja po stronie serwera generuje ciąg znaków będący JWT i wysyła go w odpowiedzi.
  3. Aplikacja użytkownika zachowuje token i używa go podczas każdej kolejnej komunikacji z serwerem wpisując go w nagłówku autoryzacji jako token „na okaziciela” (Bearer)
Authorization: Bearer <token>
  1. Aplikacja na serwerze za każdym razem sprawdza poprawność tokenu.

Podsumowanie i wnioski

JSON Web Token jest dobrym mechanizmem do komunikacji między systemami. Szczególnie wartościowy podczas weryfikacji użytkowników w żądaniach o dostęp do danych, ale nie tylko.
Jest idealnym wyborem np. dla systemów informacyjnych bazujących na headless CMS, serwujących dane do aplikacji mobilnych i serwisów WWW z dostępem do wybranych lub wszystkich treści po zalogowaniu użytkownika.