🍪 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