🍪 Cookie vs Session vs JWT

Ba cách quản lý phiên đăng nhập — ưu nhược điểm từng loại

🍪 Cookie

Dữ liệu nhỏ lưu ở browser. Server gửi, browser tự gắn kèm mỗi request.

🟢 Session

Data lưu ở server (RAM/Redis). Client chỉ giữ Session ID.

🟣 JWT

Token chứa data, self-contained. Server không cần lưu state.

1. Cookie

Cookie Flow:
1. User đăng nhập → Server xác thực thành công
2. Server gửi: Set-Cookie: userId=123; HttpOnly; Secure
3. Browser tự động lưu cookie
4. Mọi request sau → browser tự gắn: Cookie: userId=123
5. Server đọc cookie → biết user nào
// Server set cookie
res.cookie('sessionId', 'abc123', {
    httpOnly: true,     // JavaScript không đọc được (chống XSS)
    secure: true,       // Chỉ gửi qua HTTPS
    sameSite: 'strict', // Chống CSRF
    maxAge: 24 * 60 * 60 * 1000, // 24h
    path: '/'
});

2. Session (Server-Side)

Session Flow:
1. User đăng nhập → Server tạo session trong memory/Redis
2. Server gửi Session ID qua cookie: Set-Cookie: sid=xyz789
3. Mỗi request → browser gửi sid=xyz789
4. Server dùng sid → tìm session data trong store
5. Session data: { userId: 123, role: "admin", cart: [...] }
// Express + express-session + Redis
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const redis = require('redis').createClient();

app.use(session({
    store: new RedisStore({ client: redis }),
    secret: 'my-secret-key',
    resave: false,
    saveUninitialized: false,
    cookie: { secure: true, httpOnly: true, maxAge: 86400000 }
}));

// Sử dụng:
app.post('/login', (req, res) => {
    // Xác thực thành công
    req.session.userId = user.id;      // Lưu vào server
    req.session.role = user.role;
});

app.get('/profile', (req, res) => {
    if (!req.session.userId) return res.status(401).json({ error: 'Not logged in' });
    // req.session.userId → lấy từ Redis
});

3. JWT (JSON Web Token)

JWT Flow:
1. User đăng nhập → Server tạo JWT (signed token)
2. Server trả JWT cho client
3. Client lưu JWT (localStorage / cookie)
4. Mỗi request → gửi: Authorization: Bearer <jwt>
5. Server VERIFY chữ ký → lấy data từ token (KHÔNG cần DB)

JWT Structure:
  Header.Payload.Signature
  eyJhbGciOiJI... (base64)

Payload chứa:
  { "userId": 123, "role": "admin", "exp": 1708200000 }
const jwt = require('jsonwebtoken');

// Tạo JWT
app.post('/login', (req, res) => {
    const token = jwt.sign(
        { userId: user.id, role: user.role },
        process.env.JWT_SECRET,
        { expiresIn: '24h' }
    );
    res.json({ token }); // Client tự lưu
});

// Verify JWT (middleware)
function authenticate(req, res, next) {
    const token = req.headers.authorization?.split(' ')[1];
    if (!token) return res.status(401).json({ error: 'No token' });

    try {
        req.user = jwt.verify(token, process.env.JWT_SECRET);
        next(); // { userId: 123, role: "admin" }
    } catch (err) {
        res.status(401).json({ error: 'Invalid token' });
    }
}

4. Bảng So Sánh

Tiêu chí 🍪 Cookie 🟢 Session 🟣 JWT
Lưu ở đâu Browser Server (Redis) Client (localStorage/cookie)
Stateful? Stateless Stateful Stateless
Server storage Không Có (chiếm RAM) Không
Scalable Dễ Khó (shared store) Rất dễ
Revoke Xóa cookie Xóa session server Khó (phải dùng blacklist)
Size 4KB max Không giới hạn ~1-2KB
CSRF Dễ bị tấn công Dễ bị (qua cookie) An toàn (nếu dùng header)
Cross-domain Khó Khó Dễ (Bearer token)

5. Khi Nào Dùng?

Session + Cookie: Web app truyền thống (MVC), cần revoke ngay lập tức, server-rendered pages.
JWT: SPA + API, Mobile app, Microservices, cần stateless scalability.
Kết hợp: JWT trong HttpOnly cookie = tốt nhất (stateless + CSRF protection).

⚠️ Lưu ý bảo mật:
• ❌ Không lưu JWT trong localStorage (dễ bị XSS)
• ✅ Lưu JWT trong HttpOnly cookie
• ✅ Luôn dùng HTTPS + Secure flag
• ✅ Set SameSite=Strict để chống CSRF