.NET
JWT-autentykacja: kompletny przewodnik po implementacji
Implementacja bezpiecznej autentykacji to kluczowe zadanie dla nowoczesnych web API i aplikacji. JSON Web Tokens (JWT) stały się standardem branżowym dla autentykacji bezstanowej (stateless), umożliwiając serwerom weryfikację tożsamości użytkownika bez przechowywania stanu sesji po stronie serwera. Pod koniec tego przewodnika zrozumiesz pełną architekturę autentykacji JWT, będziesz w stanie zaimplementować bezpieczne wydawanie i weryfikację tokenów w swoich aplikacjach, a także otrzymasz gotową do produkcji strategię zarządzania cyklem życia tokenów. Zdobędziesz praktyczną wiedzę do bezpiecznej implementacji autentykacji JWT w dowolnym stosie technologicznym.
Czego się nauczysz
Zrozumiesz, jak architektonicznie działają JWT, dlaczego poprawna implementacja jest ważna dla bezpieczeństwa, oraz zdobędziesz praktyczne umiejętności konfiguracji middleware autentykacji, wydawania tokenów i ochrony punktów końcowych API. W efekcie będziesz gotów zaimplementować autentykację JWT w swoich projektach z pewnością co do podejmowanych decyzji dotyczących bezpieczeństwa.
Zrozumienie architektury autentykacji JWT
Zanim przejdziesz do kodu, ważne jest, aby zrozumieć, jak działa autentykacja JWT. JSON Web Token to kompaktowy, bezpieczny dla URL sposób reprezentowania oświadczeń (claims) między dwiema stronami. Token składa się z trzech części zakodowanych w Base64URL i oddzielonych kropkami: nagłówka, ładunku (payload) i podpisu (header.payload.signature).
Przepływ autentykacji JWT podąża za jasnym wzorcem: klient uwierzytelnia się (zazwyczaj przez punkt końcowy logowania), otrzymuje podpisany token, a następnie dołącza ten token do nagłówka Authorization w kolejnych żądaniach. Serwer weryfikuje podpis i oświadczenia tokena przy każdym żądaniu, co czyni system bezstanowym (stateless) — nie wymaga przechowywania stanu sesji po stronie serwera.
Kluczowe komponenty JWT
| Komponent | Przeznaczenie | Przykład |
|---|---|---|
| Nagłówek | Określa algorytm podpisu i typ tokena | {"alg":"RS256","typ":"JWT"} |
| Ładunek (Payload) | Zawiera oświadczenia (dane użytkownika, czas wygaśnięcia, emitent) | {"sub":"user123","exp":1700000000} |
| Podpis | Zapewnia integralność i autentyczność tokena | Kryptograficzny skrót nagłówka i ładunku z sekretem |
Taka konstrukcja czyni JWT idealnymi dla systemów rozproszonych i mikrousług, gdzie utrzymywanie wspólnego stanu sesji byłoby niepraktyczne.
Jak zaimplementować autentykację JWT: przewodnik krok po kroku
Poniższe kroki opisują gotowe do produkcji podejście do implementacji autentykacji JWT. Chociaż przykłady używają .NET i Node.js, zasady mają zastosowanie do wszystkich języków programowania.
Krok 1: Zainstaluj niezbędne pakiety autentykacji
Nowoczesne frameworki dostarczają oficjalne handlerów autentykacji JWT Bearer. Dla .NET zainstaluj pakiet Microsoft.AspNetCore.Authentication.JwtBearer. Dla Node.js użyj jsonwebtoken.
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
# Node.js
npm install jsonwebtoken bcryptjs dotenv
Korzystanie z oficjalnych, sprawdzonych bibliotek jest kluczowe — nigdy nie implementuj własnej logiki kryptograficznej podpisu.
Krok 2: Skonfiguruj usługi autentykacji
Konfiguracja autentykacji określa, jak Twoje API weryfikuje przychodzące tokeny. Krytyczne parametry weryfikacji obejmują:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true, // Sprawdź, czy token pochodzi od znanego emitenta
ValidateAudience = true, // Sprawdź, czy token jest przeznaczony dla tego API
ValidateLifetime = true, // Automatycznie odrzucaj wygasłe tokeny
ValidateIssuerSigningKey = true, // Sprawdź kryptograficzny podpis
ValidIssuer = "https://twoj-dostawca-tozsamosci.com",
ValidAudience = "https://twoje-api.com",
IssuerSigningKey = publicKey // Klucz do weryfikacji podpisu
};
});
Wszystkie cztery flagi weryfikacji powinny być ustawione na true w środowisku produkcyjnym. Wyłączenie któregokolwiek z nich tworzy luki w zabezpieczeniach. IssuerSigningKey może być załadowany z pliku PEM lub, w produkcji, automatycznie pobrany z punktu końcowego metadanych dostawcy tożsamości.
Krok 3: Dodaj middleware autentykacji i autoryzacji
Kolejność komponentów middleware jest krytyczna — autentykacja musi być wykonana przed autoryzacją:
app.UseAuthentication(); // Sprawdza JWT i wypełnia HttpContext.User
app.UseAuthorization(); // Stosuje polityki [Authorize] na chronionych punktach końcowych
Gdy żądanie dociera do UseAuthentication, handler sprawdza nagłówek Authorization, weryfikuje JWT i tworzy ClaimsPrincipal reprezentujący uwierzytelnionego użytkownika. Ten obiekt jest następnie dostępny w całym potoku żądania do podejmowania decyzji autoryzacyjnych.
Krok 4: Zabezpiecz punkty końcowe za pomocą atrybutu [Authorize]
Do ochrony punktów końcowych API wymagany jest atrybut [Authorize]:
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class SecureController : ControllerBase
{
[HttpGet]
public IActionResult GetSecureData()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
return Ok($"Uwierzytelniony użytkownik: {userId}");
}
}
Żądania bez ważnego tokena otrzymują odpowiedź 401 Unauthorized. Gdy autentykacja jest pomyślna, ale brakuje wymaganych uprawnień, API zwraca 403 Forbidden.
Krok 5: Zaimplementuj wydawanie tokenów (tylko do celów deweloperskich)
W środowisku produkcyjnym wydawanie tokenów należy do dedykowanego dostawcy tożsamości (IdP), implementującego OAuth 2.0 lub OpenID Connect. Rola API ogranicza się do weryfikacji.
Jednak do celów deweloperskich i testowych może być potrzebny lokalny punkt końcowy dla tokenów:
// Przykład w Node.js z jsonwebtoken
router.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ message: 'Nieprawidłowe dane logowania' });
}
const token = jwt.sign(
{ id: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '15m' } // Krótko żyjące tokeny dostępu
);
res.json({ token });
});
Tokeny dostępu powinny mieć krótki czas życia — zazwyczaj 15 minut — aby ograniczyć szkody w przypadku kradzieży tokena.
Najlepsze praktyki bezpieczeństwa dla środowiska produkcyjnego
Nigdy nie przechowuj tokenów w LocalStorage
W aplikacjach webowych przechowuj JWT w ciasteczkach HttpOnly, Secure, a nie w localStorage lub sessionStorage. Zapobiega to dostępowi do tokenów poprzez skrypty między witrynami (XSS). Konfiguracja ciasteczka powinna obejmować:
HttpOnly: true // Zapobiega dostępowi z JavaScript
Secure: true // Wysyłane tylko przez HTTPS
SameSite: Lax lub Strict // Ochrona przed CSRF
Używaj rotacji tokenów odświeżania (refresh tokens)
Ponieważ tokeny dostępu szybko wygasają, zaimplementuj mechanizm tokenów odświeżania, aby utrzymać sesje użytkowników bez ponownej autentykacji:
- Token odświeżania: Długo żyjący (np. 7 dni), bezpiecznie przechowywany na serwerze
- Token dostępu: Krótko żyjący (np. 15 minut), wysyłany z każdym żądaniem
- Rotacja: Każde żądanie odświeżenia wydaje nową parę tokenów, unieważniając stary token odświeżania
To podejście łagodzi skutki kradzieży tokenów i zapewnia czystą funkcjonalność wylogowania.
Używaj podpisu asymetrycznego (RS256/ES256) dla środowiska produkcyjnego
Używaj algorytmów asymetrycznych (RS256 lub ES256) zamiast HMAC (HS256) w produkcji:
| Algorytm | Typ klucza | Scenariusz użycia |
|---|---|---|
| HS256 | Symetryczny (wspólny sekret) | Pojedyncze aplikacje |
| RS256/ES256 | Asymetryczny (klucz publiczny/prywatny) | Systemy wielousługowe, mikrousługi |
Podpis asymetryczny pozwala wielu usługom weryfikować tokeny za pomocą klucza publicznego, podczas gdy tylko emitent posiada klucz prywatny — zgodnie z zasadą najmniejszych uprawnień.
Sprawdzaj wszystkie standardowe oświadczenia
Następujące oświadczenia są obowiązkowe dla tokenów dostępu OAuth 2.0 i powinny być weryfikowane:
iss(Emitent) — zgodny z oczekiwanym emitentemaud(Audytorium) — zgodny z identyfikatorem Twojego APIexp(Czas wygaśnięcia) — token nie wygasłsub(Podmiot) — identyfikator użytkownikaiat(Czas wydania) — token został wydany w przeszłościjti(ID JWT) — unikalny identyfikator do śledzenia tokena
Autentykacja bezstanowa z Redis
Chociaż JWT są z natury bezstanowe, implementacja unieważniania tokenów wymaga warstwy przechowywania. Redis dostarcza idealne rozwiązanie:
// Przechowywanie metadanych tokena do unieważnienia
func (r *Redis) SetJTI(ctx context.Context, key, userID string, exp time.Time) error {
return r.Client.Set(ctx, key, userID, time.Until(exp)).Err()
}
// Sprawdzenie, czy token został unieważniony
func (r *Redis) GetUserByJTI(ctx context.Context, key string) (string, error) {
return r.Client.Get(ctx, key).Result()
}
Przechowuj unikalny identyfikator tokena (jti) w Redis z TTL odpowiadającym czasowi wygaśnięcia tokena. Zapewnia to natychmiastowe wylogowanie i zapobiega ponownemu użyciu skompromitowanych tokenów.
Częste błędy w implementacji JWT, których należy unikać
Samodzielne wydawanie tokenów w produkcji: API powinny weryfikować tokeny, a nie je wydawać. Używaj dedykowanego IdP do wydawania.
Słabe klucze podpisu: Używaj kryptograficznie silnych sekretów (co najmniej 32 bajty), wygenerowanych przez
openssl rand -base64 32.Wyłączanie weryfikacji oświadczeń: Nigdy nie ustawiaj
ValidateIssuerlubValidateAudiencenafalsew produkcji.Długi czas życia tokenów: Tokeny dostępu powinny wygasać w ciągu 15 minut. Dłuższy czas życia zwiększa powierzchnię ataku.
Brak mechanizmu unieważniania: Zaimplementuj wylogowanie i unieważnianie tokenów za pomocą Redis lub podobnego magazynu.
Często zadawane pytania
Jaka jest różnica między autentykacją JWT a OAuth 2.0? JWT to format tokena, a OAuth 2.0 to framework autoryzacji. OAuth 2.0 określa, jak uzyskiwać tokeny (w tym JWT), a JWT określa strukturę samego tokena. Uzupełniają się nawzajem — OAuth 2.0 może używać JWT jako tokenów dostępu, ale możesz również używać JWT niezależnie do autentykacji w mniejszych systemach.
Jak bezpiecznie przechowywać JWT po stronie klienta? Przechowuj JWT w ciasteczkach HttpOnly, Secure, a nie w localStorage lub sessionStorage. Ciasteczka HttpOnly zapobiegają dostępowi z JavaScript, chroniąc przed atakami XSS. W aplikacjach mobilnych używaj bezpiecznych mechanizmów przechowywania specyficznych dla platformy.
Czy można unieważnić JWT przed upływem terminu ważności? Tak, poprzez prowadzenie listy unieważnień lub magazynu identyfikatorów tokenów (oświadczeń jti). Redis jest idealny do tego — przechowuj jti z TTL odpowiadającym czasowi wygaśnięcia tokena. Dla każdego żądania sprawdzaj, czy jti istnieje w magazynie unieważnień. Zapewnia to natychmiastowe wylogowanie i unieważnienie tokena.
Czy powinienem używać HS256 czy RS256 do podpisu JWT? Używaj RS256 (asymetryczny) w środowiskach produkcyjnych z wieloma usługami, gdzie wydawanie i weryfikacja tokenów odbywają się w różnych systemach. Używaj HS256 tylko w pojedynczych aplikacjach, gdzie ten sam sekret może być bezpiecznie przekazany. Podpis asymetryczny pozwala usługom weryfikującym używać tylko klucza publicznego, zgodnie z zasadą najmniejszych uprawnień.
Jak bezpiecznie zaimplementować tokeny odświeżania?
Wydaj długo żyjący token odświeżania (7 dni) wraz z krótko żyjącym tokenem dostępu (15 minut). Udostępnij punkt końcowy /refresh, który przyjmuje token odświeżania, weryfikuje go i zwraca nową parę tokenów. Rotuj tokeny odświeżania przy każdym użyciu, aby zapobiec atakom powtórzenia tokenów. Przechowuj tokeny odświeżania bezpiecznie (np. w Redis) i unieważniaj je przy wylogowaniu.
Źródła
- Duende Software. "A step by step JWT authentication example." Duende Software. 2025.
- Microsoft Learn. "Configure JWT bearer authentication in ASP.NET Core." Microsoft. 2025.
- Duende Software. "Implementing Token Authentication in Controller-Based ASP.NET Core Web APIs." Duende Software. 2026.
- QuickNode. "How to Implement JSON Web Tokens (JWT) Authorization." QuickNode. 2025.
- freeCodeCamp. "How to Build a Secure Authentication System with JWT and Refresh Tokens." freeCodeCamp. 2025.
- Vonage. "JWT Authentication in Go with Gin." Vonage Developer. 2025.
— Editorial Team
Brak komentarzy.