Przejdź do głównej zawartości

Token vs Session - porownanie

Autentykacja użytkowników w aplikacjach webowych może być realizowana na dwa fundamentalnie różne sposoby: przez sesje serwerowe (stateful) lub tokeny (stateless). Każde podejscie ma swoje zalety, wady i scenariusze, w których sprawdza się najlepiej.

Zrozumienie różnic pomaga wybrać odpowiednie rozwiązanie dla konkretnego projektu oraz unikac typowych błędów bezpieczeństwa.

Sesja to mechanizm, w którym serwer przechowuje stan uwierzytelnienia użytkownika. Po zalogowaniu serwer tworzy rekord sesji (w pamieci, bazie danych lub Redis), a klient otrzymuje tylko identyfikator sesji (session ID) przechowywany w cookie.

Cechy:

  • Dane użytkownika przechowywane na serwerze
  • Klient posiada tylko ID sesji
  • Serwer musi pamiętać wszystkie aktywne sesje
  • Sesje sa stateful (serwer ma stan)

Token (najczesciej JWT) to samodzielny nosnik informacji. Zawiera zaszyfrowane/podpisane dane użytkownika i jest przesyłany przy każdym zadaniu. Serwer nie musi przechowywac zadnego stanu - wszystko jest w tokenie.

Cechy:

  • Dane użytkownika zakodowane w tokenie
  • Klient przechowuje cały token
  • Serwer nie pamięta zadnych sesji
  • Tokeny sa stateless (serwer bez stanu)
  1. Użytkownik loguje się - wysyła login/hasło
  2. Serwer tworzy sesje - zapisuje dane w pamieci/bazie
  3. Serwer wysyła session ID - jako cookie httpOnly
  4. Przegladarka dołącza cookie - automatycznie przy każdym zadaniu
  5. Serwer sprawdza sesje - szuka danych po session ID
  6. Serwer autoryzuje - na podstawie danych sesji
┌────────┐ ┌────────┐
│ Klient │ │ Serwer │
└───┬────┘ └───┬────┘
│ │
│ POST /login {email, hasło} │
│ ──────────────────────────────────>│
│ │ ┌─────────────┐
│ │──│ Tworzenie │
│ │ │ sesji w DB │
│ │ └─────────────┘
│ Set-Cookie: session_id=abc123 │
│ <──────────────────────────────────│
│ │
│ GET /api/profile │
│ Cookie: session_id=abc123 │
│ ──────────────────────────────────>│
│ │ ┌─────────────┐
│ │──│ Szukanie │
│ │ │ sesji w DB │
│ │ └─────────────┘
│ {user: {...}} │
│ <──────────────────────────────────│
  1. Użytkownik loguje się - wysyła login/hasło
  2. Serwer generuje token - koduje dane użytkownika + podpis
  3. Klient przechowuje token - w localStorage lub cookie
  4. Klient dołącza token - w nagłówku Authorization
  5. Serwer weryfikuje podpis - bez dostepu do bazy
  6. Serwer autoryzuje - na podstawie danych z tokena
┌────────┐ ┌────────┐
│ Klient │ │ Serwer │
└───┬────┘ └───┬────┘
│ │
│ POST /login {email, hasło} │
│ ──────────────────────────────────>│
│ │ ┌─────────────┐
│ │──│ Generowanie │
│ │ │ JWT │
│ │ └─────────────┘
│ {token: "eyJhbG..."} │
│ <──────────────────────────────────│
│ │
│ GET /api/profile │
│ Authorization: Bearer eyJhbG... │
│ ──────────────────────────────────>│
│ │ ┌─────────────┐
│ │──│ Weryfikacja │
│ │ │ podpisu │
│ │ └─────────────┘
│ {user: {...}} │
│ <──────────────────────────────────│
import express from 'express';
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
const redisClient = createClient();
const app = express();
// Konfiguracja sesji
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // Niedostepne dla JavaScript
secure: true, // Tylko HTTPS
sameSite: 'strict', // Ochrona przed CSRF
maxAge: 24 * 60 * 60 * 1000 // 24 godziny
}
}));
// Logowanie
app.post('/login', async (req, res) => {
const user = await authenticateUser(req.body);
if (user) {
req.session.userId = user.id;
req.session.role = user.role;
res.json({ success: true });
} else {
res.status(401).json({ error: 'Nieprawidłowe dane' });
}
});
// Chroniony endpoint
app.get('/api/profile', (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Niezalogowany' });
}
// req.session zawiera dane użytkownika
res.json({ userId: req.session.userId });
});
// Wylogowanie
app.post('/logout', (req, res) => {
req.session.destroy(); // Usuwa sesje z Redis
res.clearCookie('connect.sid');
res.json({ success: true });
});
import express from 'express';
import jwt from 'jsonwebtoken';
const app = express();
const JWT_SECRET = process.env.JWT_SECRET;
// Logowanie
app.post('/login', async (req, res) => {
const user = await authenticateUser(req.body);
if (user) {
const token = jwt.sign(
{ sub: user.id, role: user.role },
JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ token });
} else {
res.status(401).json({ error: 'Nieprawidłowe dane' });
}
});
// Middleware weryfikacji tokena
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Brak tokena' });
}
try {
const token = authHeader.split(' ')[1];
const payload = jwt.verify(token, JWT_SECRET);
req.user = payload;
next();
} catch (error) {
res.status(401).json({ error: 'Nieprawidłowy token' });
}
}
// Chroniony endpoint
app.get('/api/profile', authMiddleware, (req, res) => {
// req.user zawiera dane z tokena
res.json({ userId: req.user.sub });
});
// Wylogowanie - klient po prostu usuwa token
// Serwer nie musi nic robić (stateless)
AspektSesja (Stateful)Token (Stateless)
Przechowywanie danychSerwer (DB/Redis)Klient (token)
SkalowalnoscWymaga współdzielonego storageŁatwa - każdy serwer weryfikuje
WylogowanieNatychmiastowe (usuniecie sesji)Trudne (token ważny do exp)
Rozmiar danychSession ID: ~32 bajtyJWT: ~500+ bajtow
WydajnoscWymaga zapytan do DB/RedisTylko weryfikacja podpisu
Cross-domainProblemy z cookiesŁatwe (Bearer token)
Mobile appsTrudniejsze (cookies)Naturalne (nagłówek)
Bezpieczeństwo CSRFWymaga tokenow CSRFOdporne (nie używa cookies)
Bezpieczeństwo XSSBezpieczniejsze (httpOnly)localStorage podatne na XSS

Wybierz sesje gdy

  • Tradycyjna aplikacja webowa (SSR)
  • Potrzebujesz natychmiastowego wylogowania
  • Masz jeden serwer lub sticky sessions
  • Nie potrzebujesz cross-domain
  • Priorytetem jest bezpieczeństwo

Wybierz tokeny gdy

  • Budujesz SPA lub aplikacje mobilna
  • Masz architekture mikroserwisowa
  • Potrzebujesz cross-domain auth
  • Skalowalnosc jest kluczowa
  • Integrujesz z third-party API

Wiele nowoczesnych aplikacji łączy oba podejscia:

// Access token (krótki, stateless) + Refresh token (długi, w DB)
interface TokenPair {
accessToken: string; // JWT, 15 min, stateless
refreshToken: string; // UUID, 7 dni, w bazie danych
}
// Refresh token można unieważnić (usunąć z bazy)
// Access token wygasa szybko, wiec skutek jest szybki
  • Sesje sa stateful - serwer pamięta stan, łatwe wylogowanie, trudniejsza skalowalnosc
  • Tokeny sa stateless - brak stanu na serwerze, łatwa skalowalnosc, trudne wylogowanie
  • Sesje lepsze dla tradycyjnych aplikacji webowych z SSR
  • Tokeny lepsze dla SPA, aplikacji mobilnych i mikroserwisow
  • W praktyce czesto stosuje się hybrydy (access + refresh tokens)
  • Bezpieczeństwo zależy od implementacji, nie od samego wyboru technologii