W nowoczesnych aplikacjach internetowych bezpieczeństwo i kontrola dostępu są kluczowe. Jednym z popularnych rozwiązań, które pomaga zapewnić bezpieczną autentykację użytkowników, jest JWT (JSON Web Token). JWT to standard pozwalający na przekazywanie informacji o użytkowniku w postaci cyfrowego tokenu. W tym artykule wyjaśnię, czym jest JWT, jak działa oraz jak użyć go w C#, aby stworzyć bezpieczny, bezstanowy system logowania w aplikacjach .NET.
Co to jest JWT i dlaczego warto go używać?
JWT (JSON Web Token) to otwarty standard służący do bezpiecznego przenoszenia informacji między dwiema stronami – np. między serwerem a klientem. Informacje te są zapisane jako claims (twierdzenia) wewnątrz tokenu.
JWT składa się z trzech głównych części:
• Header (nagłówek) – zawiera informacje o algorytmie podpisu i typie tokenu (zwykle JWT).
• Payload (ładunek) – zawiera claims, czyli dane (np. identyfikator użytkownika, role, itp.).
• Signature (podpis) – kryptograficzny podpis tokenu, który zapewnia jego integralność.
Header i Payload są zakodowane w formacie Base64Url, co oznacza, że każdy może je odczytać po dekodowaniu. Nie są one zaszyfrowane, dlatego nie powinno się umieszczać w nich poufnych informacji. Signature natomiast jest wygenerowany przy użyciu tajnego klucza (lub klucza publiczno-prywatnego), dzięki czemu serwer może zweryfikować, czy token nie został zmodyfikowany. Jeśli ktoś spróbuje zmienić zawartość tokenu, podpis przestanie się zgadzać, a serwer odrzuci taki token.
Jakie są zalety JWT? Przede wszystkim, jest to mechanizm bezstanowy. Oznacza to, że serwer nie musi przechowywać informacji o sesji użytkownika w bazie czy pamięci – wszystkie potrzebne dane (twierdzenia o użytkowniku) są zawarte w samym tokenie. Pozwala to budować łatwo skalowalne API bez konieczności współdzielenia stanu sesji między serwerami. Dodatkowo JWT jest niezależny od platformy i języka – to po prostu tekstowy token (w formacie JSON), więc może być używany w komunikacji między różnymi systemami.
Jak działa uwierzytelnianie przy użyciu JWT?
Wyobraźmy sobie typowy scenariusz logowania z JWT:
1. Logowanie: Użytkownik przesyła swoje dane logowania (np. login i hasło) do serwera.
2. Generowanie tokenu: Serwer sprawdza poprawność danych. Jeśli są poprawne, generuje token JWT zawierający informacje o użytkowniku (np. jego ID lub nazwę, rolę). Token zostaje podpisany tajnym kluczem znanym tylko serwerowi.
3. Przekazanie tokenu: Token JWT jest zwracany do klienta (np. aplikacji front-end). Klient może przechować go np. w pamięci aplikacji lub w lokalnym magazynie.
4. Wykorzystanie tokenu: Przy kolejnych zapytaniach do chronionego API, klient dołącza otrzymany token do każdego żądania, zwykle w nagłówku HTTP Authorization: Bearer <TOKEN>.
5. Weryfikacja po stronie serwera: Serwer, otrzymując żądanie z tokenem, weryfikuje podpis tokenu (za pomocą tego samego tajnego klucza, którym go podpisano). Jeśli token jest prawidłowy i nie wygasł, serwer uznaje, że żądanie pochodzi od uwierzytelnionego użytkownika i udziela dostępu do żądanych zasobów. Nie trzeba przy tym odwoływać się do bazy danych po dane sesyjne – wystarczy zaufanie do informacji w tokenie.
6. Wygaśnięcie tokenu: Tokeny JWT mają zwykle ustawiony czas ważności (np. 1 godzina). Po wygaśnięciu token staje się nieważny i użytkownik musi zalogować się ponownie, aby otrzymać nowy token. To zabezpieczenie na wypadek kradzieży tokenu – ogranicza czas, przez jaki skradziony token byłby użyteczny.
Ten przepływ pozwala aplikacjom działać w modelu bezstanowym i odciąża serwer z zarządzania sesjami. JWT świetnie sprawdza się np. w architekturze REST API oraz w aplikacjach mobilnych czy SPA (Single Page Application), gdzie trzymanie sesji po stronie serwera byłoby uciążliwe.
JWT w C# – implementacja i przykład
Platforma .NET udostępnia gotowe narzędzia do pracy z JWT, co znacznie ułatwia ich wykorzystanie. Najpopularniejszym podejściem w aplikacjach ASP.NET Core jest skorzystanie z wbudowanego mechanizmu JWT Bearer Authentication, gdzie konfigurujemy usługę uwierzytelniania w Startup.cs lub w konfiguracji buildera (w nowszych wersjach .NET) podając m.in. klucz i parametry tokenu. Warto jednak zrozumieć, co dzieje się pod spodem. Poniżej znajduje się prosty przykład wygenerowania tokenu JWT w czystym C#:
using System;
using System.Text;
using System.Security.Claims;
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
string tajnyKlucz = "mojSuperTajnyKlucz12345"; /* Klucz używany do podpisu (w praktyce przechowuj go bezpiecznie) */
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tajnyKlucz));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
/* Tworzymy przykładowe "claims" dla tokenu (np. nazwa użytkownika i rola) */
var claims = new List<Claim> {
new Claim(ClaimTypes.Name, "Jan Kowalski"),
new Claim(ClaimTypes.Role, "Administrator")
};
/* Generowanie tokenu JWT */
var token = new JwtSecurityToken(
issuer: "moja-aplikacja",
audience: "moja-aplikacja",
claims: claims,
expires: DateTime.Now.AddHours(1),
signingCredentials: credentials);
/* Serializacja tokenu do postaci tekstowej (string) */
string tokenString = new JwtSecurityTokenHandler().WriteToken(token);
Console.WriteLine(tokenString);Powyższy kod tworzy token JWT podpisany algorytmem HMAC SHA256. Ustawiamy w nim kilka claims (imię użytkownika oraz rolę), ustawiamy wydawcę (issuer), odbiorcę (audience) oraz czas wygaśnięcia tokenu (tu 1 godzina). W realnej aplikacji takie generowanie tokenu wykonywałby nasz serwer po poprawnym zalogowaniu użytkownika. Używamy klasy JwtSecurityTokenHandler do wygenerowania finalnego ciągu znaków stanowiącego token. Taki token klient otrzymałby po zalogowaniu i przesyłał w nagłówkach kolejnych żądań.
Po stronie serwera weryfikacja tokenu odbywa się automatycznie, jeśli korzystamy z wbudowanego mechanizmu JWT Bearer Authentication w ASP.NET Core – wystarczy odpowiednio skonfigurować usługę uwierzytelniania (podając ten sam tajny klucz, informacje o wydawcy/odbiorcy itp.). Gdy konfiguracja jest poprawna, framework sam sprawdzi sygnaturę i ważność tokenu dla każdego przychodzącego żądania. W kodzie możemy potem pobrać informacje o zalogowanym użytkowniku z kontekstu (np. HttpContext.User), gdzie dostępne są claims z tokenu.
Podsumowanie
JSON Web Token jest obecnie standardowym rozwiązaniem do realizacji uwierzytelniania i autoryzacji w aplikacjach webowych i mobilnych. Dzięki JWT możemy tworzyć bezpieczne i skalowalne mechanizmy logowania bez utrzymywania sesji po stronie serwera. W ekosystemie .NET integracja JWT jest prostsza dzięki gotowym bibliotekom i wsparciu w frameworku.
Jeśli temat JWT Cię zainteresował i chcesz poznać więcej praktycznych aspektów tworzenia aplikacji w C#, warto kontynuować naukę. Jeśli dopiero zaczynasz i chcesz zostać programistą .NET, zapraszam Cię do mojego szkolenia online "Zostań Programistą .NET" – kompleksowego programu, który przeprowadzi Cię od zera do pierwszej pracy jako młodszy programista C#/.NET w 3 miesiące. Tam zgłębisz nie tylko JWT, ale także wiele innych kluczowych technologii i narzędzi, które wykorzystasz w codziennej pracy programisty.