Współczesne aplikacje webowe coraz częściej opierają się na tokenach JWT (JSON Web Token) jako podstawowym mechanizmie uwierzytelniania. W środowisku Next.js, które łączy renderowanie po stronie serwera (SSR) i po stronie klienta (CSR), właściwe zarządzanie sesją wymaga przemyślanej architektury. Niniejszy artykuł przedstawia model zagrożenia, opisuje działanie mechanizmu, pokazuje kontrast pomiędzy podatnym a poprawionym kodem oraz dostarcza checklistę mitygacji zgodną ze standardami OWASP, CWE i zasadą least‑privilege.
Model zagrożenia – co może pójść nie tak?
Podstawowe wektory ataku na systemy oparte na JWT to:
- kradzież tokenu przez XSS (skrypt wstrzyknięty w przeglądarce odbiera token z pamięci lokalnej lub z ciasteczka niechronionego);
- atak CSRF, gdy przeglądarka automatycznie wysyła ciasteczko z tokenem w żądaniu do API;
- replay attack – przechwycony token jest ponownie używany przed wygaśnięciem;
- brak rotacji kluczy – w razie wycieku jednorazowo można zablokować wszystkie tokeny.
Model ten można zwizualizować jako trzy warstwy: przechowywanie tokenu, jego weryfikacja oraz zarządzanie cyklem życia. Każda warstwa wymaga odrębnych zabezpieczeń.
Jak działa mechanizm JWT w Next.js?
Typowy przepływ wygląda następująco:
- Użytkownik loguje się, serwer (Node.js) generuje dwa tokeny –
accessToken(krótki czas życia) irefreshToken(dłuższy). - Tokeny są wysyłane do przeglądarki w ciasteczkach
HttpOnlyiSecure. - Podczas żądania API przeglądarka automatycznie dołącza
accessToken. Middleware w Next.js weryfikuje podpis i datę wygaśnięcia. - Gdy
accessTokenwygasa, aplikacja wywołuje endpoint odświeżania, podającrefreshToken. Serwer zwraca nowyaccessTokeni, opcjonalnie, nowyrefreshToken.
Kluczowe jest, aby tokeny nie były dostępne w JavaScript (stąd HttpOnly) i aby każdy endpoint wymagał weryfikacji podpisu przy użyciu aktualnego klucza.
Przykład: podatny vs. poprawiony kod
Poniżej zestawiamy dwa fragmenty – najpierw prosty, ale niebezpieczny sposób przechowywania tokenu w localStorage, a potem zalecaną implementację z ciasteczkami HttpOnly oraz middleware w Next.js.
// PODATNY – przechowywanie w localStorage (wrażliwe na XSS)
function login(credentials) {
fetch('/api/auth/login', {method: 'POST', body: JSON.stringify(credentials)})
.then(r => r.json())
.then(data => {
localStorage.setItem('accessToken', data.accessToken);
localStorage.setItem('refreshToken', data.refreshToken);
});
}
// POPRAWIONY – ustawianie HttpOnly cookies po stronie serwera
// pages/api/auth/login.js
import { sign } from 'jsonwebtoken';
export default async function handler(req, res) {
const { username, password } = req.body;
// ... weryfikacja credentials ...
const accessToken = sign({ sub: username }, process.env.JWT_ACCESS_SECRET, { expiresIn: '15m' });
const refreshToken = sign({ sub: username }, process.env.JWT_REFRESH_SECRET, { expiresIn: '30d' });
res.setHeader('Set-Cookie', [
`accessToken=${accessToken}; HttpOnly; Secure; SameSite=Strict; Path=/api; Max-Age=900`,
`refreshToken=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh; Max-Age=2592000`
]);
res.status(200).json({ message: 'Logged in' });
}
W wersji poprawionej tokeny nie trafiają do JavaScriptu, a flagi SameSite=Strict ograniczają ryzyko CSRF. Dodatkowo, ograniczamy ścieżkę (Path) tak, aby ciasteczka były wysyłane wyłącznie do dedykowanych endpointów.
Checklisty mitygacji – co zrobić, aby spełnić OWASP i CWE
- Przechowywanie: używaj
HttpOnly+Securecookies; unikajlocalStorageisessionStoragedla tokenów. - Ograniczenie domeny i ścieżki: ustaw
DomainiPathtak, aby ciasteczko było dostępne tylko dla API. - SameSite: wybierz
StrictlubLaxw zależności od potrzeb, aby chronić przed CSRF. - Walidacja: weryfikuj podpis, datę wygaśnięcia i claimy (
iss,aud) po stronie serwera. - Rotacja kluczy: regularnie zmieniaj sekrety JWT; przechowuj poprzedni klucz przez krótki okres, aby umożliwić weryfikację istniejących tokenów.
- Odświeżanie tokenów: stosuj krótkie
accessTokeni dłuższerefreshToken; po udanym odświeżeniu unieważniaj poprzedni refresh token. - Rate limiting i monitorowanie nieudanych weryfikacji – redukuje ryzyko brute‑force i token‑replay.
Implementacja powyższych punktów odpowiada na CWE‑287 (Improper Authentication) oraz CWE‑352 (Cross‑Site Request Forgery).
Strategia rotacji kluczy JWT
Klucze używane do podpisywania tokenów powinny mieć określony okres ważności (np. 30 dni). Proces rotacji obejmuje:
- Wygenerowanie nowego klucza i zapisanie go w bezpiecznym magazynie (np. AWS KMS, HashiCorp Vault).
- Dodanie nowego klucza do listy „akceptowanych” w konfiguracji aplikacji, zachowując poprzedni klucz jako „fallback”.
- Po upływie okresu przejściowego (np. 24 h) usunięcie starego klucza – wszystkie tokeny podpisane starszym kluczem przestaną być akceptowane.
W Next.js można to zrealizować przy pomocy funkcji pomocniczej:
// utils/jwt.js
import jwt from 'jsonwebtoken';
const keys = {
current: process.env.JWT_ACCESS_SECRET,
previous: process.env.JWT_ACCESS_SECRET_OLD,
};
export function verify(token) {
try {
return jwt.verify(token, keys.current);
} catch (e) {
// fallback to previous key
return jwt.verify(token, keys.previous);
}
}
Takie podejście spełnia wymóg OWASP „Key Management” i minimalizuje ryzyko przerwania sesji podczas rotacji.
Typowe błędy i kompromisy
Even experienced teams make mistakes. Najczęstsze to:
- Ustawienie zbyt długiego czasu życia access tokena – zwiększa powierzchnię ataku w przypadku kradzieży.
- Brak odświeżania refresh tokena – pozwala na nieograniczone wykorzystanie skradzionego tokena.
- Użycie jednego klucza dla access i refresh tokenów – utrudnia rotację i zwiększa konsekwencje wycieku.
- Nieodpowiednie flagi SameSite – przy
Nonetokeny są narażone na CSRF w kontekście cross‑site. - Eksponowanie tokenów w logach – szczególnie w środowiskach CI/CD.
Rozwiązania kompromisowe, takie jak „sliding expiration” dla refresh tokena, mogą pomóc, ale zawsze należy ocenić wpływ na bezpieczeństwo i użyteczność.
Najlepsza obrona to nie tylko technologia, ale świadome projektowanie sesji od samego początku.
Praktyczna checklista wdrożeniowa
- ✅ Użyj
HttpOnly,SecureiSameSite=Strictprzy ustawianiu ciasteczek z JWT. - ✅ Ogranicz
Pathciasteczek do endpointów API. - ✅ Implementuj middleware w Next.js, które weryfikuje token przy każdym żądaniu.
- ✅ Stwórz endpoint odświeżania tokenów z rotacją refresh tokena.
- ✅ Wdroż rotację kluczy co 30‑60 dni, zachowując poprzedni klucz jako fallback.
- ✅ Monitoruj nieudane weryfikacje i stosuj rate limiting.
- ✅ Regularnie przeglądaj kod pod kątem XSS i usuwaj wszystkie niebezpieczne wstawienia danych.
Spełniając powyższą listę, zespół inżynierski tworzy solidny fundament bezpieczeństwa sesji, zgodny ze standardami OWASP Top 10 oraz CWE‑798 (Use of Hard‑coded Credentials).
Podsumowując, bezpieczne tokeny JWT w Next.js wymagają przemyślanej strategii przechowywania, regularnej rotacji kluczy oraz ścisłej kontroli nad cyklem życia tokenów. Implementując opisane praktyki, ograniczysz ryzyko kradzieży tokenów, ataków CSRF i XSS, a także zapewnisz zgodność z najlepszymi standardami branżowymi. Jeśli potrzebujesz wsparcia przy audycie bezpieczeństwa lub wdrożeniu takiego systemu w Twojej organizacji, skontaktuj się z Coderia.it – pomożemy zbudować rozwiązanie, które spełni najwyższe wymogi bezpieczeństwa i wydajności.



