Bảo mật trong lưu trữ JWT token (access token / refresh token) , so sánh chi tiết giữa cookie và localStorage
Khi nói đến bảo mật trong lưu trữ JWT token (access token / refresh token) trong trình duyệt, bạn cần cân nhắc giữa bảo mật (security) và trải nghiệm người dùng (UX). Dưới đây là so sánh chi tiết giữa cookie và localStorage, và gợi ý lựa chọn tốt nhất:
🔒 Cookie vs. localStorage
Tiêu chí | Cookie (HttpOnly) | localStorage |
---|---|---|
Bảo mật trước XSS | ✅ Rất an toàn nếu HttpOnly |
❌ Dễ bị đánh cắp nếu XSS xảy ra |
Bảo mật trước CSRF | ❌ Dễ bị tấn công nếu không có CSRF protection | ✅ Không tự động gửi nên tránh được CSRF |
Tự động gửi khi request | ✅ (được gửi trong mỗi HTTP request) | ❌ Phải đính kèm token thủ công (JS) |
Dễ sử dụng trong frontend | ❌ Khó thao tác nếu HttpOnly | ✅ Truy cập dễ dàng trong JS |
Khả năng cấu hình expire | ✅ Có thể đặt expires , max-age |
❌ Không expire tự động |
Dung lượng lưu trữ | ~4KB | ~5MB |
✅ Gợi ý tốt nhất (Best practice):
🔐 Dùng Cookie với flag HttpOnly
, Secure
, và SameSite=Strict hoặc Lax
- Access Token: không nên lưu nếu token quá ngắn hạn hoặc không muốn tự động gửi trong mọi request → bạn có thể lưu ở memory runtime.
- Refresh Token: lưu ở HttpOnly cookie → tránh bị truy cập bởi JS nếu có lỗ hổng XSS.
Tại sao?
HttpOnly
giúp ngăn JavaScript truy cập token → bảo vệ khỏi XSS.Secure
đảm bảo chỉ gửi qua HTTPS.SameSite
giúp giảm thiểu rủi ro CSRF.
🔄 Mô hình đề xuất:
-
Khi user đăng nhập → backend gửi access token và refresh token:
Access token
: dùng cho các API call, lưu tạm trong memory (RAM) frontend.Refresh token
: lưu trongHttpOnly Secure Cookie
.
-
Khi access token hết hạn:
- Gửi request đến
/refresh-token
→ backend kiểm tra cookie và trả lại access token mới.
- Gửi request đến
❌ Khi nào KHÔNG nên dùng localStorage?
- Nếu ứng dụng của bạn có dữ liệu quan trọng hoặc cần bảo vệ nghiêm ngặt → KHÔNG dùng localStorage để lưu token, vì:
- Token có thể bị đánh cắp nếu có XSS dù chỉ 1 dòng.
Flow chart: Mô hình lưu JWT an toàn trong Next.js
[User Login Form]
|
v
[POST /api/login] <--- credentials (username, password)
|
v
[Next.js API Route xử lý login]
|
|--> Verify credentials
|--> Generate Access Token (short-lived)
|--> Generate Refresh Token (long-lived)
|
|--> Đặt Refresh Token vào HttpOnly Cookie
|--> Trả Access Token trong JSON response
|
v
[Client nhận access token] --> Lưu trong memory (RAM, state, hoặc React context)
|
v
[Client gọi API protected]
|
|--> Đính kèm access token trong Authorization Header
|
v
[API xác thực access token]
|
v
[Trả về dữ liệu nếu token hợp lệ]
=== Khi access token hết hạn ===
[Client gửi request đến /api/refresh-token]
|
v
[Next.js API Route đọc refresh token từ cookie]
|
|--> Verify refresh token
|--> Tạo access token mới
|
v
[Trả về access token mới]
|
v
[Client cập nhật lại access token trong memory]
✅ Tổng kết:
Trường hợp | Khuyến nghị |
---|---|
Ứng dụng có mức bảo mật cao | Cookie HttpOnly |
Ứng dụng đơn giản, cần nhanh gọn | Có thể dùng localStorage (rất hạn chế) |
Dùng OAuth, refresh token | Cookie HttpOnly cho refresh token + memory cho access token |
All rights reserved