HTTP là giao thức không trạng thái (stateless): mỗi request là một cuộc gặp độc lập, server không tự nhớ request trước là của ai. Vậy làm sao một website biết bạn đã đăng nhập, giữ giỏ hàng của bạn qua nhiều trang? Câu trả lời là cookie — cơ chế đơn giản nhưng là nơi tập trung nhiều lỗ hổng bảo mật nhất nếu dùng sai. Bài này đo thật luồng session bằng curl, rồi mổ ba thuộc tính cookie quyết định an toàn: HttpOnly, Secure, SameSite.

Luồng cơ bản gồm hai chiều:

  • Server → client: gửi header Set-Cookie: session=abc123; ... trong response (thường sau khi đăng nhập).
  • Client → server: từ đó về sau, trình duyệt tự động đính Cookie: session=abc123 vào mọi request tới cùng site — không cần code gì thêm.

session=abc123 thường chỉ là một session id ngẫu nhiên; dữ liệu phiên thật (bạn là ai, quyền gì) lưu ở phía server, id chỉ là chìa khóa tra cứu. Nhờ vậy request sau server biết đây là ai.

Set-Cookie: session=abc123; Max-Age=3600; Path=/; HttpOnly; SameSite=Lax
Cookie: session=abc123        # client tự gửi lại

Ảnh chụp đoạn mã nền tối giải thích cookie và session cách web nhớ bạn giữa các request, HTTP không có trạng thái cookie là cách vá mỗi request độc lập server không tự biết ai gửi server gửi Set-Cookie session abc123 client tự động gửi lại ở mọi request sau tới cùng site Cookie session abc123 session id trỏ tới dữ liệu phiên lưu ở server ai quyền, các thuộc tính an toàn của cookie HttpOnly JS không đọc được document.cookie chống XSS trộm cookie, Secure chỉ gửi qua HTTPS không lộ trên mạng, SameSite Lax Strict None chống CSRF không gửi kèm request chéo site, Max-Age thời hạn giây Max-Age 0 xoá cookie, Path Domain phạm vi cookie được gửi kèm, xem bằng curl jar lưu cookie curl -c jar.txt login lưu Set-Cookie vào jar curl -b jar.txt me gửi cookie từ jar curl -D xem header Set-Cookie

Hình 1: Server gửi Set-Cookie, client tự gửi lại Cookie ở mọi request sau — vá tính không-trạng-thái. Ba thuộc tính HttpOnly/Secure/SameSite quyết định an toàn.

Đo thật: luồng session

Dựng một server có /login (cấp session), /me (đọc cookie để nhận diện), /logout (xóa cookie). Dùng curl với cookie jar (-c lưu, -b gửi):

Ảnh chụp bảng kết quả đo thật nền tối chạy curl 7.88 và socket, phần một chưa có cookie /me ra 401 chưa đăng nhập curl /me không cookie ra HTTP 401, phần hai /login server gửi Set-Cookie đầy đủ thuộc tính curl -c jar.txt /login Set-Cookie session 9a9cd579ea41 Max-Age 3600 Path slash HttpOnly SameSite Lax jar lưu HttpOnly gạch dưới 127.0.0.1 session 9a9cd579ea41, phần ba gửi lại cookie /me nhận diện được user curl -b jar.txt /me Xin chao an_nguyen session 9a9cd579ea41 server tra session id biết đây là ai, phần bốn /logout Set-Cookie Max-Age 0 trình duyệt xoá cookie Set-Cookie session bằng rỗng Max-Age 0 Path slash

Hình 2: Chưa cookie thì /me trả 401. Sau /login, server gửi Set-Cookie: session=9a9cd579ea41 kèm HttpOnly; SameSite=Lax; curl lưu vào jar (đánh dấu #HttpOnly_). Gửi lại cookie thì /me nhận diện an_nguyen. /logout gửi Max-Age=0 để xóa.

Kết quả xác nhận đúng luồng: request /me không cookie → 401. Sau /login, server trả Set-Cookie: session=9a9cd579ea41; Max-Age=3600; Path=/; HttpOnly; SameSite=Lax, curl lưu vào jar (nó ghi cả cờ #HttpOnly_). Gửi lại cookie đó, /me trả Xin chao an_nguyen — server tra session id và biết đây là ai. /logout gửi Set-Cookie: session=; Max-Age=0 — Max-Age=0 bảo trình duyệt xóa cookie ngay.

Ba thuộc tính bảo mật sống còn

Cookie chứa session id là chìa khóa danh tính, nên trộm được cookie là chiếm được phiên. Ba thuộc tính bảo vệ nó:

  • HttpOnly: JavaScript không đọc được cookie (document.cookie không thấy nó). Đây là phòng tuyến chống XSS: kể cả kẻ tấn công chèn được script vào trang, nó cũng không đọc và gửi cookie session đi được. Cookie phiên gần như luôn nên có HttpOnly.
  • Secure: cookie chỉ được gửi qua HTTPS. Không có nó, một request HTTP (hoặc bị hạ cấp) sẽ để lộ cookie trên đường truyền cho ai nghe lén.
  • SameSite: kiểm soát cookie có được gửi kèm khi request đến từ site khác hay không. Strict (không gửi với mọi request chéo site), Lax (gửi với điều hướng cấp cao như click link, mặc định của trình duyệt hiện đại), None (gửi mọi lúc — bắt buộc kèm Secure). Đây là phòng tuyến chống CSRF: kẻ tấn công ở site khác không kích được request "thay bạn" mang theo cookie.

Đánh đổi và lưu ý

Session ở server vs JWT không trạng thái. Mẫu trên (session id + dữ liệu ở server) dễ thu hồi (xóa session ở server là đăng xuất mọi nơi) nhưng cần lưu trữ phiên (bộ nhớ/Redis/DB). Lựa chọn khác là token tự chứa (JWT) đặt trong cookie: không cần tra server nhưng khó thu hồi trước hạn. Chọn theo nhu cầu thu hồi và quy mô.

Kích thước và số lượng cookie. Cookie đi kèm mọi request tới site, nên cookie to = tốn băng thông mỗi lần. Giới hạn thực tế ~4KB mỗi cookie. Đừng nhét dữ liệu lớn vào cookie; để id, dữ liệu để server.

SameSite=Lax là mặc định nhưng đừng ỷ lại. Trình duyệt hiện đại mặc định Lax khi không khai, nhưng khai tường minh vẫn tốt hơn (rõ ý, tránh khác biệt giữa trình duyệt). Và SameSite không thay thế được token CSRF cho các luồng nhạy cảm — nó là một lớp, không phải tất cả.

Ba ý mang về

  1. Cookie vá tính không-trạng-thái của HTTP: server gửi Set-Cookie, client tự đính Cookie ở mọi request sau — đo thật, /me không cookie trả 401, sau /login có session thì nhận diện được user; session id là chìa khóa trỏ tới dữ liệu phiên ở server.
  2. Ba thuộc tính bảo mật là bắt buộc với cookie phiên: HttpOnly (JS không đọc được → chống XSS trộm cookie), Secure (chỉ HTTPS), SameSite (chống CSRF request chéo site).
  3. Biết đánh đổi: session-ở-server dễ thu hồi nhưng cần lưu trữ, JWT không trạng thái thì ngược lại; cookie đi kèm mọi request nên giữ nhỏ (id thôi); SameSite=Lax là mặc định nhưng vẫn nên khai rõ và không thay hẳn token CSRF.

Nguồn

Phần sau ta quay lại tối ưu băng thông: nén nội dung — Accept-Encoding, Content-Encoding, so gzip với brotli và đo tỉ lệ nén thật trên một trang HTML.