Cookies, Session, Token — 3 cách web 'nhớ' bạn đã đăng nhập

HTTP vốn không có trí nhớ — mỗi request là một lần gặp lạ. Cookies, Session và Token là 3 cách web 'nhớ' bạn giữa các request: cái nào bảo mật hơn, cái nào nhanh hơn, và vì sao web hiện đại thường dùng cả 3.

10 phút đọcCập nhật 2026-05-22
Nghe bài viết
Để Claude đọc bài cho bạn — vừa nghe vừa làm việc khác
Mục lục bài viết(21)
Sơ đồ HTTP cookie — browser nhận Set-Cookie từ server response, gửi lại Cookie ở các request sau

Sơ đồ trao đổi HTTP cookie — bước 1: browser gửi request chưa có cookie; bước 2: server response kèm Set-Cookie header; bước 3+: browser tự động gửi cookie đó kèm mỗi request tiếp theo cho cùng domain. Đây là cơ chế gốc làm nền cho mọi hệ thống login web. Nguồn: Wikimedia Commons (public domain).

Hiểu đơn giản nhất

Web hoạt động trên HTTP — giao thức không có trí nhớ. Mỗi lần bạn click một link là một lần gặp lạ với server: server không nhớ bạn là ai, vừa làm gì.

Vậy tại sao bạn login Shopee buổi sáng, đến tối mở lại vẫn thấy "Chào bạn, Nguyễn Văn A"?

Vì có 3 kỹ thuật giúp web "nhớ" bạn giữa các request:

Kỹ thuậtAnalogy đời thườngNơi lưu thông tin
CookieNhãn dán trên áo khi vào nhà hàng buffetTrên máy bạn (browser)
SessionSố phòng khách sạn (server tra số → ra tên khách)Trên server (DB hoặc cache)
TokenVé concert có hologram, bảo vệ chỉ check chữ kýTrong cookie hoặc localStorage

Cả 3 kỹ thuật không loại trừ nhau — web hiện đại thường dùng kết hợp. Ví dụ: Session ID lưu trong Cookie có flag bảo mật, còn API riêng dùng JWT Token.

Tại sao bạn cần biết

  • Đọc được lỗi auth dev báo cáo. "Cookie expired" = vé hết hạn, "Invalid session" = số phòng không tồn tại, "JWT signature mismatch" = vé bị giả mạo. Hiểu khác biệt = không bị dev lừa "lỗi này khó lắm".
  • Chọn đúng kiểu auth khi đặt SaaS. Web admin nội bộ → Session. Public API cho mobile/extension → Token. Chọn sai = code phức tạp gấp đôi.
  • Audit security tự tin. Cookie thiếu HttpOnly là lỗi cấp High trong OWASP. Đọc Network tab DevTools → thấy ngay web nào ẩu, web nào chuẩn.
  • Hiểu vì sao app bạn dùng "đăng xuất khắp các máy" hay "ghi nhớ đăng nhập 30 ngày". Đó là server invalidate session toàn cục, hoặc set expiry refresh token. Concept gốc nằm trong bài này.

Cookie là một chuỗi text ngắn server gửi cho browser, browser tự động lưu và gửi kèm mọi request tiếp theo tới cùng domain.

Bước 1: Bạn vào shopee.vn lần đầu. Browser gửi request:

GET /home HTTP/1.1
Host: shopee.vn

Bước 2: Server response kèm Set-Cookie:

HTTP/1.1 200 OK
Set-Cookie: cart_id=abc123; Path=/; Max-Age=2592000; HttpOnly; Secure
Set-Cookie: visitor_id=xyz789; Path=/; Max-Age=86400

Bước 3: Browser tự động lưu 2 cookie đó. Lần sau (kể cả tuần sau), khi bạn truy cập shopee.vn, browser tự kèm:

GET /products/123 HTTP/1.1
Host: shopee.vn
Cookie: cart_id=abc123; visitor_id=xyz789

Server đọc cart_id=abc123 → biết đây là giỏ hàng của bạn → load đúng giỏ.

FlagNghĩaVì sao quan trọng
HttpOnlyJavaScript trong trang không đọc được cookieChống XSS đánh cắp session
SecureChỉ gửi qua HTTPSChống nghe lén WiFi công cộng
SameSite=Lax/StrictKhông gửi cookie khi click link từ domain khácChống CSRF (giả mạo request)
Max-Age / ExpiresThời gian sống của cookieHết hạn → browser tự xoá

Cookie cho login PHẢI có cả 4 flag. Cookie thiếu HttpOnly là lỗ XSS phổ biến nhất — hacker chèn được 1 dòng JS là đánh cắp session toàn bộ user.

  • Mỗi cookie tối đa 4 KB
  • Mỗi domain tối đa ~50 cookie
  • Browser gửi cookie kèm mọi request tới domain đó → cookie càng to càng làm web chậm

Vì vậy cookie chỉ lưu (chuỗi ngẫu nhiên ngắn), không lưu data thực. Data thực lưu ở server (session) hoặc trong token (JWT).

Session — số phòng khách sạn

Session là dữ liệu user lưu ở server, mỗi user có một Session ID ngẫu nhiên đại diện.

Flow session-based auth

  1. User nhập email + password → backend verify → tạo session: {user_id: 123, role: "ADMIN", login_at: ...} lưu trong DB hoặc Redis với key sess_abc123xyz
  2. Backend trả về browser cookie: Set-Cookie: session_id=sess_abc123xyz; HttpOnly; Secure
  3. Mỗi request sau, browser kèm cookie → backend đọc session_id → tra DB lấy data session → biết user là ai
  4. Logout → backend xoá session khỏi DB → cookie kia thành "số phòng không tồn tại"

Ưu / nhược

Ưu:

  • Revoke nhanh — xoá 1 row trong DB là user bị đá khỏi hệ thống ngay (kể cả khi user chưa close browser)
  • Data ở server → cookie nhỏ (chỉ chứa session ID), không lộ thông tin user
  • Có thể update permission realtime — admin đổi role → session reload từ DB → permission mới effective ngay

Nhược:

  • Tốn DB/Redis lookup mỗi request → 5-20ms thêm latency mỗi API call
  • Khó scale ngang — nếu có 5 server, session phải share qua Redis chung, không thể lưu in-memory per server
  • Stateful — server "có trí nhớ", reset server là mất session (trừ khi lưu Redis riêng)

Pattern session ở vietcodex.com

lib/auth.ts cấu hình better-auth với cookieCache 60 giây — middleware đọc cookie verify chữ ký, không cần lookup DB mỗi request, chỉ refresh sau 60 giây để catch role change. Cân bằng giữa performance và revoke responsiveness.

Token (JWT) — vé concert có hologram

JWT (JSON Web Token) là một chuỗi tự chứa thông tin — server không cần lookup DB, chỉ verify chữ ký là biết user.

Cấu trúc JWT

JWT gồm 3 phần, ngăn cách bởi dấu chấm:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJBRE1JTiIsImV4cCI6MTcxNjAwMDAwMH0.5K8N9p_x...
└──────── Header ────────┘  └────────── Payload ──────────┘  └── Signature ──┘
PhầnNội dungEncoded thế nào
Header{"alg": "HS256", "typ": "JWT"} — thuật toán kýbase64
Payload{"user_id": 123, "role": "ADMIN", "exp": 1716000000} — claimsbase64
SignatureHMAC-SHA256(header + payload, SECRET)base64

⚠️ Payload chỉ encode base64, KHÔNG mã hoá — ai cũng decode được. Đừng nhồi password hay thông tin riêng tư vào payload. Chỉ để user_id, role, expiry.

Flow JWT auth

  1. User login → backend verify → tạo JWT: ký bằng SECRET (chỉ server biết) → trả về frontend
  2. Frontend lưu JWT vào cookie (HttpOnly) hoặc localStorage
  3. Mỗi request kèm JWT trong header: Authorization: Bearer eyJhbGci...
  4. Backend nhận JWT → tách signature → verify bằng SECRET → nếu khớp → tin payload → biết user là ai, KHÔNG cần lookup DB
  5. JWT hết hạn (theo claim exp) → reject, yêu cầu login lại hoặc refresh

Ưu / nhược

Ưu:

  • Stateless — server không lưu gì, scale ngang dễ
  • Không lookup DB mỗi request → API public nhanh hơn
  • Cross-domain — frontend ở app.vietcodex.com, API ở api.vietcodex.com đều dùng được token

Nhược:

  • Khó revoke trước hạn — token đã phát → đến hết exp mới chết. Muốn đá user ngay phải duy trì blacklist (mất stateless).
  • Payload lộ — đừng để thông tin nhạy cảm
  • SECRET lộ = toàn hệ thống lộ — ai có SECRET có thể ký token giả mạo bất kỳ user nào. Phải rotate SECRET định kỳ.

Khi nào dùng JWT vs Session

ScenarioKhuyến nghị
Web app admin/dashboardSession (revoke nhanh quan trọng)
Public API cho mobile appJWT (stateless + fast)
Mobile app nativeJWT + refresh token
Browser extensionJWT (extension không share cookie với web)
Microservices nội bộ gọi nhauJWT (service-to-service auth)
SaaS như vietcodex.comCả 2 — cookie session cho dashboard, JWT cho public API/webhook

Ví dụ thực tế: Bạn login Facebook trên web vs mobile

Web (facebook.com)

  1. Bạn nhập email + password trên facebook.com/login
  2. Server verify → tạo session ID trong Redis cluster
  3. Server set cookie: Set-Cookie: c_user=12345; HttpOnly; Secure; SameSite=Lax; Domain=.facebook.com; Max-Age=86400
  4. Browser tự gửi cookie mỗi lần bạn vào facebook.com hay messenger.com (subdomain) trong 24h tới
  5. Bấm "Đăng xuất" → server invalidate session ID → cookie kia vô dụng

Mobile app (Facebook iOS/Android)

  1. Bạn login → server tạo 2 token: access (1 giờ) + refresh (60 ngày)
  2. App lưu cả 2 trong secure storage (Keychain iOS / EncryptedSharedPreferences Android)
  3. Mỗi API call kèm Authorization: Bearer <access_token>
  4. Access token hết hạn → app dùng refresh token gọi /auth/refresh → nhận access token mới
  5. 60 ngày không mở app → refresh token hết hạn → app yêu cầu login lại
  6. Bạn report mất điện thoại → admin Facebook revoke refresh token đó từ server

Lưu ý sự khác biệt: web dùng cookie + session, mobile dùng token. Cùng 1 nhà cung cấp, 2 cơ chế khác nhau cho 2 platform khác nhau.

Cái gì có thể sai

Vấn đềNguyên nhânCách fix
"Session expired" sau 30 phút không hoạt độngServer set Session timeout 30 phútTăng timeout hoặc implement keep-alive
Logout ở 1 tab, tab khác vẫn loginCookie chưa expire, browser cacheForce reload toàn site, hoặc dùng BroadcastChannel sync logout
Login lúc HTTPS, sang HTTP mất sessionCookie Secure flag không gửi qua HTTPForce toàn site HTTPS (web hiện đại nên 100% HTTPS)
Mobile app "tự đăng xuất" sau vài ngàyRefresh token hết hạn hoặc bị server revokeTăng expires_in refresh token, hoặc auto-relogin với face ID
JWT token "không revoke được"Đặc tính của JWT — chỉ hết hạn tự nhiênImplement blacklist trong Redis hoặc giảm exp xuống 5-15 phút
"Session fixation attack"Server tái dùng session ID sau loginSau login PHẢI regenerate session ID mới
Cookie "không gửi" qua API gọi cross-domainSameSite=Strict hoặc thiếu CORS credentialsĐổi sang SameSite=Lax + fetch(..., {credentials: 'include'})

Tóm tắt 1 dòng

Cookie = nhãn dán trên áo, Session = số phòng khách sạn (data ở server), Token = vé có hologram (data tự chứa). Web hiện đại dùng cả 3: Cookie HttpOnly+Secure để chứa Session ID cho dashboard, JWT cho API public. Cookie thiếu HttpOnly = lỗ XSS lớn nhất hệ thống auth.

Đọc tiếp

Câu hỏi thường gặp

Cookies có nguy hiểm không? Có phải Cookies là cách 'theo dõi người dùng' mà GDPR cấm?
Cookies có 2 loại: first-party (do chính web bạn đang truy cập tạo — thường để login, giữ giỏ hàng) và third-party (do quảng cáo Facebook/Google chèn vào để theo dõi xuyên trang). GDPR + EU ePrivacy chỉ siết third-party tracking — vì thế bạn thấy banner 'Accept cookies' khắp nơi. First-party cookies cho login/giỏ hàng vẫn được phép mặc định. Chrome đang dần xoá third-party cookies (kế hoạch 2025-2026). Web Việt Nam đa số dùng first-party hợp pháp.
Session và Token cái nào an toàn hơn?
Cả hai đều an toàn nếu cài đúng. Session ID an toàn hơn về mặt 'revoke' — admin xoá session bên server là user bị đá ngay. Token JWT khó revoke trước hạn (phải duy trì blacklist). Đổi lại, Token không cần lookup database mỗi request → nhanh hơn 10-100ms với high-traffic API. Quy tắc: web app người dùng admin/staff → Session. Public API + mobile app cần stateless → Token. SaaS thường dùng cả 2: cookie session cho web dashboard, JWT token cho API public.
JWT là gì? Khác token thường ở chỗ nào?
JWT (JSON Web Token) là một CHUẨN cho token — gồm 3 phần ngăn cách bởi dấu chấm: Header (loại token + thuật toán ký), Payload (thông tin user: id, role, expiry), Signature (chữ ký bằng secret key của server). Server chỉ cần verify signature → biết token thật, không cần hỏi DB. 'Token thường' (opaque token) là chuỗi ngẫu nhiên, server phải tra DB để biết user nào. JWT phổ biến trong API stateless, opaque token phổ biến trong session-based auth.
Tại sao password không lưu trong cookie?
Vì cookie gửi đi gửi lại mỗi request → ai bắt được 1 request là biết password. Cách đúng: user nhập password 1 lần, server verify rồi tạo session ID hoặc token bỏ vào cookie. Cookie chỉ chứa 'vé' (random string vô nghĩa), không chứa password gốc. Mất cookie → hacker login được tài khoản đó, nhưng không biết password thật → các tài khoản khác (Gmail, ngân hàng) của bạn vẫn an toàn. Đây là lý do nên dùng password khác nhau cho mỗi web.
Cookie HttpOnly và Secure flag có quan trọng không?
Rất quan trọng. HttpOnly = JavaScript không đọc được cookie → chống XSS đánh cắp session. Secure = chỉ gửi cookie qua HTTPS → chống nghe lén ở WiFi công cộng. SameSite=Lax = không gửi cookie sang domain khác → chống CSRF. better-auth (vietcodex.com dùng) tự set cả 3 flag này. Nếu web bạn login mà cookie không có 3 flag này → ưu tiên fix vì lỗi này dễ bị tận dụng nhất khi audit security.
Logout xoá cookie có đủ không?
Chưa đủ nếu dùng Token JWT. Xoá cookie chỉ làm browser quên token đó, nhưng nếu hacker đã copy token từ trước, token vẫn còn dùng được đến hết hạn (thường 1-7 ngày). Cách đúng: (1) backend invalidate session ID trong DB (Session-based); hoặc (2) thêm token vào blacklist tới hết hạn (JWT). Cookie session-based logout đầy đủ hơn JWT — đó là một lý do admin panel hay dùng session thay vì JWT.
Refresh token là gì? Khác access token thế nào?
Access token hạn ngắn (15 phút - 1 giờ), dùng để gọi API. Refresh token hạn dài (7-30 ngày), chỉ dùng để xin access token mới khi access token hết hạn. Pattern này cân bằng giữa security (token bị lộ chỉ dùng được 15 phút) và UX (user không phải đăng nhập lại mỗi giờ). Mobile app, OAuth flow đều dùng pattern này. Web app session-based đơn giản thì không cần — cookie tự refresh expiry mỗi request.