← Trở về trang chủ

HTTP Headers

HTTP Headers là gì?

Định nghĩa: HTTP Headers là các metadata (dữ liệu về dữ liệu) được gửi kèm với HTTP request và response. Headers cung cấp thông tin bổ sung về request/response hoặc về dữ liệu được gửi trong message body.

Cấu trúc Headers

Headers có dạng key-value pairs:

Header-Name: Header-Value

Ví dụ minh họa đơn giản

HTTP Request với Headers:

GET /api/users HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: application/json
Authorization: Bearer abc123token
Content-Type: application/json

HTTP Response với Headers:

HTTP/1.1 200 OK
Date: Mon, 15 Jan 2025 10:00:00 GMT
Content-Type: application/json
Content-Length: 348
Cache-Control: max-age=3600
Set-Cookie: session_id=xyz789; HttpOnly

{
  "id": 1,
  "name": "Nguyễn Văn A"
}

Vai trò của Headers

  • Truyền thông tin bổ sung: Gửi metadata mà không cần thêm vào body
  • Authentication: Xác thực người dùng qua Authorization header
  • Content Negotiation: Cho server biết client muốn nhận dữ liệu dạng gì
  • Caching: Kiểm soát cách cache dữ liệu
  • Security: Bảo vệ ứng dụng web khỏi các lỗ hổng bảo mật
  • CORS: Kiểm soát cross-origin requests
  • Tracking: Theo dõi requests qua X-Request-ID

Phân loại Headers

Loại Mô tả Ví dụ
Request Chỉ xuất hiện trong request Accept, User-Agent, Referer
Response Chỉ xuất hiện trong response Set-Cookie, Server, ETag
Both Có thể xuất hiện ở cả hai Content-Type, Cache-Control
Entity Headers Thông tin về body của message Content-Length, Content-Encoding
⚠️ Lưu ý:
  • Header names không phân biệt hoa thường (case-insensitive): Content-Type = content-type
  • Header values có phân biệt hoa thường (case-sensitive)
  • Có thể có nhiều headers cùng tên (ví dụ: nhiều Set-Cookie)
  • Custom headers thường bắt đầu với X- (ví dụ: X-API-Key)

Lý do thiết kế Headers tách biệt khỏi Body

1. Separation of Concerns (Tách biệt trách nhiệm)

Metadata vs Data:

  • Headers: Chứa thông tin VỀ dữ liệu (metadata)
  • Body: Chứa dữ liệu thực tế (actual data)
// Headers = metadata
Content-Type: application/json
Content-Length: 58
Authorization: Bearer token123

// Body = actual data
{
  "name": "Nguyễn Văn A",
  "email": "vana@example.com"
}

✓ Dễ đọc, dễ maintain, logic rõ ràng

2. Flexibility (Tính linh hoạt)

Có thể thêm/sửa metadata mà không cần thay đổi format của body:

// Request 1: Chỉ gửi data
POST /api/users
Content-Type: application/json

{"name": "User A"}

// Request 2: Thêm authentication (không cần sửa body!)
POST /api/users
Content-Type: application/json
Authorization: Bearer abc123

{"name": "User A"}

// Request 3: Thêm tracking (vẫn không cần sửa body!)
POST /api/users
Content-Type: application/json
Authorization: Bearer abc123
X-Request-ID: req-12345
X-Client-Version: 2.0.1

{"name": "User A"}

3. Security (Bảo mật)

Xử lý authentication và authorization riêng biệt:

  • Headers: Chứa tokens, API keys → Dễ kiểm tra ở middleware
  • Body: Chứa business data → Chỉ xử lý sau khi authenticated
// ✓ Đúng: Auth ở header
POST /api/orders
Authorization: Bearer token123
Content-Type: application/json

{
  "product_id": 101,
  "quantity": 2
}

// ✗ Sai: Đừng đặt auth token trong body
POST /api/orders
Content-Type: application/json

{
  "token": "abc123",        // ← Không nên!
  "product_id": 101,
  "quantity": 2
}

Lợi ích:

  • Middleware có thể kiểm tra auth trước khi parse body
  • Tokens không bị log khi log request body
  • Dễ dàng implement security policies

4. Performance (Hiệu năng)

Caching thông qua headers:

// Server gửi response với cache headers
HTTP/1.1 200 OK
Cache-Control: max-age=3600
ETag: "v1-abc123"
Content-Type: application/json

{"products": [...]}

// Lần sau client gửi request
GET /api/products
If-None-Match: "v1-abc123"

// Server chỉ cần check header, không cần xử lý body
HTTP/1.1 304 Not Modified
ETag: "v1-abc123"

(không có body, tiết kiệm bandwidth!)

Compression thông qua headers:

// Client yêu cầu compressed data
GET /api/large-data
Accept-Encoding: gzip, deflate

// Server nén và báo trong header
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Length: 1024  // ← Kích thước sau khi nén

[compressed binary data]  // ← Tiết kiệm 70-90% bandwidth

5. Extensibility (Khả năng mở rộng)

Dễ dàng thêm custom headers khi cần:

// Thêm tracking
X-Request-ID: req-abc-123
X-Correlation-ID: corr-xyz-789

// Thêm versioning
X-API-Version: 2.0
Accept-Version: 2.0

// Thêm debugging
X-Debug-Mode: true
X-Response-Time: 125ms

// Thêm business logic
X-Tenant-ID: company-123
X-Feature-Flags: feature-a,feature-b

✓ Không cần thay đổi API contract
✓ Backward compatible
✓ Dễ dàng A/B testing

6. Protocol Design (Thiết kế giao thức)

HTTP được thiết kế theo nguyên tắc này từ đầu:

  • Stateless: Mỗi request độc lập, cần đầy đủ thông tin
  • Layered: Headers cho transport layer, body cho application layer
  • Uniform Interface: Cùng một format cho mọi loại data
Ví dụ: Cùng một endpoint, nhiều loại response
// Client 1 muốn JSON
GET /api/users/1
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json
{"id": 1, "name": "User A"}

// Client 2 muốn XML
GET /api/users/1
Accept: application/xml

HTTP/1.1 200 OK
Content-Type: application/xml
<user><id>1</id><name>User A</name></user>

// Chỉ khác header Accept, không cần 2 endpoints!

So sánh: Nếu không có Headers?

// ✗ Không có headers (mọi thứ trong body)
POST /api/users

{
  "auth_token": "abc123",
  "request_id": "req-123",
  "accept_format": "json",
  "cache_control": "no-cache",
  "user_agent": "MyApp/1.0",
  "actual_data": {
    "name": "User A",
    "email": "usera@example.com"
  }
}

// ✓ Có headers (tách biệt rõ ràng)
POST /api/users
Authorization: Bearer abc123
X-Request-ID: req-123
Accept: application/json
Cache-Control: no-cache
User-Agent: MyApp/1.0

{
  "name": "User A",
  "email": "usera@example.com"
}

Vấn đề khi không có headers:

  • ❌ Body bị "ô nhiễm" với metadata
  • ❌ Khó validate và parse
  • ❌ Không thể kiểm tra auth trước khi parse body
  • ❌ Mỗi API có thể có format khác nhau
  • ❌ Không tương thích với caching, compression
  • ❌ Khó debug và monitor

Authorization

Mục đích: Gửi credentials để xác thực với server.

Cú pháp: Authorization: <type> <credentials>

Các loại Authorization

// 1. Basic Authentication
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
// (base64 encoded "username:password")

// 2. Bearer Token (JWT)
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

// 3. API Key
Authorization: ApiKey abc123xyz789

// 4. Digest
Authorization: Digest username="user", realm="api@example.com", ...

// 5. OAuth 2.0
Authorization: Bearer ya29.a0AfH6SMBx...

Ví dụ với Playwright

import { test, expect } from '@playwright/test';

test('GET với Bearer token', async ({ request }) => {
  const response = await request.get('https://api.example.com/users/me', {
    headers: {
      'Authorization': 'Bearer abc123token'
    }
  });

  expect(response.status()).toBe(200);
});

test('GET với Basic Auth', async ({ request }) => {
  const credentials = Buffer.from('username:password').toString('base64');

  const response = await request.get('https://api.example.com/data', {
    headers: {
      'Authorization': `Basic ${credentials}`
    }
  });

  expect(response.status()).toBe(200);
});

API-Key / X-API-Key

Alternative cách gửi API key (không theo chuẩn Authorization):

GET /api/weather
X-API-Key: your-api-key-here
Host: api.weather.com

Cookie

Gửi cookies đã được lưu từ server:

GET /api/dashboard
Cookie: session_id=abc123; user_pref=dark_mode
Host: example.com
⚠️ Best Practices:
  • Luôn dùng HTTPS khi gửi Authorization header
  • Không log Authorization header
  • Bearer token nên có expiration time
  • API keys nên rotate định kỳ

Accept

Cho server biết client muốn nhận dữ liệu dạng gì:

// Muốn JSON
Accept: application/json

// Muốn XML
Accept: application/xml

// Muốn HTML
Accept: text/html

// Chấp nhận nhiều loại (có priority)
Accept: application/json, application/xml;q=0.9, */*;q=0.8

Accept-Language

Ngôn ngữ mà client muốn nhận:

// Muốn tiếng Việt
Accept-Language: vi-VN

// Muốn tiếng Anh hoặc tiếng Việt
Accept-Language: en-US, vi-VN;q=0.9

// Muốn tiếng Việt, tiếng Anh, hoặc bất kỳ
Accept-Language: vi, en;q=0.8, *;q=0.5

Accept-Encoding

Compression algorithms mà client hỗ trợ:

Accept-Encoding: gzip, deflate, br

// br = Brotli (nén tốt nhất)
// gzip = phổ biến nhất
// deflate = cũ hơn

Content-Type (trong request)

Cho server biết dữ liệu gửi lên có format gì:

// Gửi JSON
POST /api/users
Content-Type: application/json

{"name": "User A"}

// Gửi form data
POST /api/upload
Content-Type: application/x-www-form-urlencoded

name=UserA&email=user@example.com

// Upload file
POST /api/upload
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="photo.jpg"
...

Ví dụ Content Negotiation với Playwright

import { test, expect } from '@playwright/test';

test('Request JSON response', async ({ request }) => {
  const response = await request.get('https://api.example.com/users/1', {
    headers: {
      'Accept': 'application/json'
    }
  });

  expect(response.headers()['content-type']).toContain('application/json');
  const data = await response.json();
  expect(data).toHaveProperty('id', 1);
});

test('Request XML response', async ({ request }) => {
  const response = await request.get('https://api.example.com/users/1', {
    headers: {
      'Accept': 'application/xml'
    }
  });

  expect(response.headers()['content-type']).toContain('application/xml');
  const text = await response.text();
  expect(text).toContain('<user>');
});

test('POST với Content-Type', async ({ request }) => {
  const response = await request.post('https://api.example.com/users', {
    headers: {
      'Content-Type': 'application/json'
    },
    data: {
      name: 'Nguyễn Văn A',
      email: 'vana@example.com'
    }
  });

  expect(response.status()).toBe(201);
});

Quality Values (q)

Chỉ định mức độ ưu tiên (0.0 - 1.0, default = 1.0):

Accept: application/json;q=1.0, application/xml;q=0.8, text/html;q=0.5

// Nghĩa là:
// 1. Ưu tiên JSON nhất (q=1.0)
// 2. XML cũng ok (q=0.8)
// 3. HTML cũng được nhưng không thích lắm (q=0.5)

User-Agent

Thông tin về client (browser, app, bot, ...):

// Chrome browser
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36

// Firefox browser
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0

// Mobile Safari
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1

// Custom app
User-Agent: MyApp/1.0 (iOS 17.0; iPhone 15 Pro)

// Playwright
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36

Custom User-Agent với Playwright

test('Custom User-Agent', async ({ request }) => {
  const response = await request.get('https://api.example.com/data', {
    headers: {
      'User-Agent': 'MyTestBot/1.0'
    }
  });

  expect(response.status()).toBe(200);
});

Referer (hoặc Referrer)

URL của trang trước đó mà user đến từ đó:

GET /api/products/123
Referer: https://example.com/products
Host: api.example.com

// Server biết user đang xem product 123 từ trang danh sách products
Note: "Referer" là lỗi chính tả trong HTTP spec (đúng phải là "Referrer"), nhưng đã được giữ lại vì lý do tương thích.

Host

Domain name của server (bắt buộc trong HTTP/1.1):

GET /api/users
Host: api.example.com

// Nếu có port khác 80/443
GET /api/users
Host: api.example.com:8080

Origin

Origin của request (dùng cho CORS):

POST /api/users
Origin: https://myapp.com
Host: api.example.com

// Server sẽ check Origin để quyết định có cho phép CORS không

Sự khác biệt: Host vs Origin

  • Host: Server mà request đang gửi đến
  • Origin: Nơi mà request xuất phát (dùng cho CORS)
// User đang ở https://myapp.com, gọi API của https://api.example.com

POST https://api.example.com/users
Host: api.example.com     // ← Server đích
Origin: https://myapp.com // ← Nơi xuất phát

If-None-Match

Gửi ETag đã có, hỏi server có version mới không:

// Lần 1: GET và nhận ETag
GET /api/products/1
→ Response:
HTTP/1.1 200 OK
ETag: "v1-abc123"
{"id": 1, "name": "Product A"}

// Lần 2: Gửi lại với If-None-Match
GET /api/products/1
If-None-Match: "v1-abc123"

→ Response (nếu chưa thay đổi):
HTTP/1.1 304 Not Modified
ETag: "v1-abc123"
(không có body, tiết kiệm bandwidth!)

If-Modified-Since

Chỉ lấy nếu đã thay đổi sau thời điểm này:

// Lần 1
GET /api/report.pdf
→ Response:
HTTP/1.1 200 OK
Last-Modified: Mon, 15 Jan 2025 10:00:00 GMT
[PDF data]

// Lần 2
GET /api/report.pdf
If-Modified-Since: Mon, 15 Jan 2025 10:00:00 GMT

→ Response (nếu chưa update):
HTTP/1.1 304 Not Modified
Last-Modified: Mon, 15 Jan 2025 10:00:00 GMT
(không có body)

Cache-Control (request)

Điều khiển caching behavior:

// Không muốn dùng cache
GET /api/latest-news
Cache-Control: no-cache

// Chỉ muốn fresh data (không cũ hơn 60s)
GET /api/stock-price
Cache-Control: max-age=60

// Không cache gì cả
GET /api/sensitive-data
Cache-Control: no-store

Test caching với Playwright

import { test, expect } from '@playwright/test';

test('Test ETag caching', async ({ request }) => {
  // Lần 1: Lấy data và ETag
  const response1 = await request.get('https://api.example.com/products/1');
  expect(response1.status()).toBe(200);

  const etag = response1.headers()['etag'];
  console.log('ETag:', etag);

  // Lần 2: Gửi với If-None-Match
  const response2 = await request.get('https://api.example.com/products/1', {
    headers: {
      'If-None-Match': etag
    }
  });

  // Nếu không đổi, nhận 304
  if (response2.status() === 304) {
    console.log('Cache hit! Data chưa thay đổi');
  } else if (response2.status() === 200) {
    console.log('Cache miss! Data đã thay đổi');
  }
});

test('Force no-cache', async ({ request }) => {
  const response = await request.get('https://api.example.com/latest-data', {
    headers: {
      'Cache-Control': 'no-cache'
    }
  });

  expect(response.status()).toBe(200);
  // Luôn nhận fresh data
});

X-Request-ID

Unique ID để tracking request qua nhiều services:

GET /api/orders/123
X-Request-ID: req-abc-123-xyz-789
Authorization: Bearer token123

// Server log:
[req-abc-123-xyz-789] Processing order 123
[req-abc-123-xyz-789] Calling payment service
[req-abc-123-xyz-789] Payment successful
[req-abc-123-xyz-789] Response sent

X-Forwarded-For

IP address gốc của client khi đi qua proxy/load balancer:

// Client → Proxy → Load Balancer → Server

X-Forwarded-For: 123.45.67.89, 10.0.0.1, 10.0.0.2
// 123.45.67.89 = Client IP gốc
// 10.0.0.1 = Proxy IP
// 10.0.0.2 = Load Balancer IP

X-Client-Version

Version của client app (useful cho mobile apps):

GET /api/features
X-Client-Version: 2.1.0
X-Platform: iOS
X-OS-Version: 17.0

// Server có thể:
// - Trả về features tương thích với version 2.1.0
// - Báo user update app nếu version quá cũ
// - Track bugs theo version

Other Custom Headers

// Tenant ID cho multi-tenant apps
X-Tenant-ID: company-abc-123

// Feature flags
X-Feature-Flags: new-ui,beta-feature

// Debug mode
X-Debug-Mode: true

// Correlation ID (distributed tracing)
X-Correlation-ID: corr-xyz-789

// Rate limiting info
X-RateLimit-Remaining: 95
X-RateLimit-Limit: 100

Ví dụ với Playwright

import { test, expect } from '@playwright/test';

test('Send custom headers', async ({ request }) => {
  const response = await request.get('https://api.example.com/data', {
    headers: {
      'X-Request-ID': `req-${Date.now()}`,
      'X-Client-Version': '2.1.0',
      'X-Platform': 'Web',
      'X-Feature-Flags': 'new-ui,dark-mode'
    }
  });

  expect(response.status()).toBe(200);

  // Kiểm tra response có trả về request ID không
  const requestId = response.headers()['x-request-id'];
  console.log('Request ID:', requestId);
});
⚠️ Lưu ý về Custom Headers:
  • Tên thường bắt đầu với X- (convention, không bắt buộc)
  • Cần document rõ ràng cho team
  • Có thể bị strip bởi proxies/CDNs
  • CORS: Cần thêm vào Access-Control-Allow-Headers nếu dùng cross-origin

Content-Type

Loại dữ liệu trong response body:

// JSON
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id": 1, "name": "User A"}

// HTML
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
<html>...</html>

// Plain text
HTTP/1.1 200 OK
Content-Type: text/plain
Hello World

// PDF
HTTP/1.1 200 OK
Content-Type: application/pdf
[PDF binary data]

// Image
HTTP/1.1 200 OK
Content-Type: image/jpeg
[JPEG binary data]

Content-Length

Kích thước của response body (bytes):

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 58

{"id": 1, "name": "User A", "email": "usera@example.com"}

Content-Encoding

Compression algorithm đã dùng:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: gzip
Content-Length: 1024

[gzip compressed data]

// Client sẽ tự động decompress

Content-Language

Ngôn ngữ của content:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Language: vi-VN

<html>
  <h1>Xin chào</h1>
</html>

Test Content Headers với Playwright

import { test, expect } from '@playwright/test';

test('Verify Content headers', async ({ request }) => {
  const response = await request.get('https://api.example.com/users/1');

  // Kiểm tra Content-Type
  expect(response.headers()['content-type']).toContain('application/json');

  // Kiểm tra Content-Length
  const contentLength = parseInt(response.headers()['content-length']);
  expect(contentLength).toBeGreaterThan(0);

  // Verify body size matches Content-Length
  const body = await response.text();
  expect(body.length).toBe(contentLength);
});

Cache-Control

Điều khiển caching behavior:

// Cache trong 1 giờ
Cache-Control: max-age=3600

// Không cache
Cache-Control: no-store

// Cache nhưng phải revalidate
Cache-Control: no-cache

// Private cache (chỉ browser, không qua CDN)
Cache-Control: private, max-age=3600

// Public cache (browser + CDN)
Cache-Control: public, max-age=86400

// Kết hợp nhiều directives
Cache-Control: public, max-age=31536000, immutable

ETag

Identifier cho version của resource:

HTTP/1.1 200 OK
ETag: "v1-abc123"
Cache-Control: max-age=3600
Content-Type: application/json

{"id": 1, "name": "Product A"}

// Client sẽ lưu ETag và gửi lại lần sau với If-None-Match

Expires

Thời điểm cache hết hạn (HTTP/1.0 legacy):

HTTP/1.1 200 OK
Expires: Wed, 15 Jan 2025 11:00:00 GMT
Content-Type: application/json

// Cache-Control ưu tiên hơn Expires trong HTTP/1.1

Last-Modified

Lần cuối resource được modified:

HTTP/1.1 200 OK
Last-Modified: Mon, 15 Jan 2025 10:00:00 GMT
Cache-Control: max-age=3600

// Client có thể dùng If-Modified-Since lần sau

Cache Strategy Example

// Static assets (CSS, JS, images) - cache 1 năm
HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutable
ETag: "v2-xyz789"
Content-Type: application/javascript

// API data - cache 5 phút, must revalidate
HTTP/1.1 200 OK
Cache-Control: private, max-age=300, must-revalidate
ETag: "data-v1-abc"
Content-Type: application/json

// Sensitive data - không cache
HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, must-revalidate
Content-Type: application/json

Cache-Control Directives

Directive Ý nghĩa
max-age=<seconds> Cache trong bao lâu
no-cache Phải revalidate trước khi dùng cache
no-store Không cache gì cả
public Có thể cache ở CDN, proxy
private Chỉ cache ở browser
must-revalidate Phải check với server khi stale
immutable Không bao giờ thay đổi

Strict-Transport-Security (HSTS)

Bắt buộc dùng HTTPS:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

// Browser sẽ:
// - Chỉ dùng HTTPS cho domain này trong 1 năm
// - Áp dụng cho cả subdomains
// - Có thể thêm vào HSTS preload list

X-Content-Type-Options

Ngăn MIME type sniffing:

X-Content-Type-Options: nosniff

// Browser sẽ không đoán Content-Type, phải tin header

X-Frame-Options

Ngăn clickjacking (không cho embed trong iframe):

// Không cho embed trong iframe
X-Frame-Options: DENY

// Chỉ cho same-origin
X-Frame-Options: SAMEORIGIN

// Cho phép từ domain cụ thể
X-Frame-Options: ALLOW-FROM https://trusted-site.com

Content-Security-Policy (CSP)

Kiểm soát nguồn tài nguyên được load:

// Chỉ cho phép resources từ same origin
Content-Security-Policy: default-src 'self'

// Cho phép scripts từ self và Google Analytics
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.google-analytics.com

// Chi tiết hơn
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'unsafe-inline' https://cdn.example.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';

X-XSS-Protection

Bật XSS filter của browser (legacy):

X-XSS-Protection: 1; mode=block

// 1 = enable
// mode=block = block page nếu detect XSS

Security Headers Best Practice

HTTP/1.1 200 OK
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
⚠️ Important:
  • Security headers nên set cho TẤT CẢ responses
  • Test kỹ CSP trước khi deploy (có thể break site)
  • Dùng tools như securityheaders.com để check

Access-Control-Allow-Origin

Cho phép origins nào được truy cập:

// Cho phép tất cả origins (không an toàn!)
Access-Control-Allow-Origin: *

// Cho phép một origin cụ thể
Access-Control-Allow-Origin: https://myapp.com

// Cho phép với credentials
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true

Access-Control-Allow-Methods

Methods được phép trong CORS:

Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

Access-Control-Allow-Headers

Headers được phép gửi:

Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With

Access-Control-Max-Age

Thời gian cache preflight request (giây):

Access-Control-Max-Age: 86400
// Cache preflight trong 24 giờ

Access-Control-Expose-Headers

Headers nào client có thể đọc:

Access-Control-Expose-Headers: X-Request-ID, X-RateLimit-Remaining

CORS Flow Example

// Client (https://myapp.com) gửi request đến API (https://api.example.com)

// 1. Preflight request (browser tự động gửi)
OPTIONS /api/users
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

// 2. Server response
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

// 3. Browser thấy OK, gửi actual request
POST /api/users
Origin: https://myapp.com
Authorization: Bearer token123
Content-Type: application/json

{"name": "User A"}

// 4. Server response
HTTP/1.1 201 Created
Access-Control-Allow-Origin: https://myapp.com
Content-Type: application/json

{"id": 1, "name": "User A"}
⚠️ CORS Best Practices:
  • Không dùng * cho production APIs
  • Whitelist cụ thể các origins
  • Nếu dùng credentials, không được dùng *
  • Set Access-Control-Max-Age để giảm preflight requests

Server

Thông tin về web server:

Server: nginx/1.21.0
Server: Apache/2.4.41 (Ubuntu)
Server: Microsoft-IIS/10.0
Server: cloudflare
Security Note: Nên ẩn hoặc giảm thông tin server để tránh lộ version (attackers có thể exploit known vulnerabilities).
// ✗ Không tốt
Server: Apache/2.4.41 (Ubuntu)

// ✓ Tốt hơn
Server: Apache

// ✓ Tốt nhất
(không có Server header)

Date

Thời gian server gửi response:

Date: Mon, 15 Jan 2025 10:00:00 GMT

Location

URL redirect hoặc URL của resource mới tạo:

// Redirect (3xx)
HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/new-url

// Resource mới tạo (201)
HTTP/1.1 201 Created
Location: /api/users/123
Content-Type: application/json

{"id": 123, "name": "User A"}

Set-Cookie

Gửi cookies cho client lưu:

// Cookie đơn giản
Set-Cookie: session_id=abc123

// Cookie với options
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=3600

// Multiple cookies
Set-Cookie: session_id=abc123; HttpOnly
Set-Cookie: user_pref=dark_mode; Max-Age=2592000

Cookie Attributes

Attribute Ý nghĩa
HttpOnly JavaScript không đọc được (chống XSS)
Secure Chỉ gửi qua HTTPS
SameSite=Strict Chống CSRF, không gửi cross-site
SameSite=Lax Gửi với top-level navigation
Max-Age Thời gian sống (giây)
Expires Thời điểm hết hạn
Domain Domain nào nhận cookie
Path Path nào nhận cookie

Vary

Headers nào ảnh hưởng đến cache:

// Cache riêng cho mỗi Accept-Encoding
Vary: Accept-Encoding

// Cache riêng cho mỗi User-Agent và Accept-Language
Vary: User-Agent, Accept-Language

// Thường dùng với CORS
Vary: Origin

So sánh Request vs Response Headers

Category Request Headers Response Headers
Authentication Authorization, Cookie Set-Cookie, WWW-Authenticate
Content Content-Type, Accept Content-Type, Content-Length, Content-Encoding
Caching If-None-Match, If-Modified-Since ETag, Last-Modified, Cache-Control, Expires
CORS Origin Access-Control-Allow-*
Client Info User-Agent, Referer, Host Server, Date
Security - Strict-Transport-Security, CSP, X-Frame-Options

Common Mistakes (Lỗi thường gặp)

1. Gửi sensitive data trong headers có thể bị log

// ✗ Sai: Password trong custom header
X-User-Password: mypassword123

// ✓ Đúng: Dùng Authorization với HTTPS
Authorization: Bearer encrypted-token

2. Quên set Content-Type

// ✗ Sai: Không có Content-Type
POST /api/users

{"name": "User A"}

// ✓ Đúng
POST /api/users
Content-Type: application/json

{"name": "User A"}

3. CORS: Dùng * với credentials

// ✗ Sai: Không được phép!
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

// ✓ Đúng
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true

4. Không set security headers

// ✗ Thiếu security headers
HTTP/1.1 200 OK
Content-Type: text/html

<html>...</html>

// ✓ Đầy đủ security headers
HTTP/1.1 200 OK
Content-Type: text/html
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'

<html>...</html>

5. Cache sensitive data

// ✗ Sai: Cache user private data
GET /api/users/me
→
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600  // ← Nguy hiểm!

// ✓ Đúng
GET /api/users/me
→
HTTP/1.1 200 OK
Cache-Control: private, no-store

Best Practices

✓ Request Headers

  • Luôn gửi Content-Type khi có body
  • Dùng Accept để specify format mong muốn
  • Set User-Agent để server biết client là gì
  • Dùng Authorization header cho auth, không đặt trong body
  • Include X-Request-ID để tracking
  • Dùng If-None-Match / If-Modified-Since để tận dụng cache

✓ Response Headers

  • Luôn set Content-Type
  • Set caching headers phù hợp (Cache-Control, ETag)
  • Include tất cả security headers
  • Set CORS headers đúng cách
  • Dùng Location cho redirects và resources mới tạo
  • Set Content-Length khi biết size
  • Ẩn hoặc giảm thông tin trong Server header

✓ Security

  • Luôn dùng HTTPS
  • Set Strict-Transport-Security
  • Set Content-Security-Policy
  • Set X-Frame-Options: DENY hoặc SAMEORIGIN
  • Set X-Content-Type-Options: nosniff
  • Cookies: Dùng HttpOnly, Secure, SameSite
  • Không log sensitive headers

✓ Performance

  • Enable compression với Accept-EncodingContent-Encoding
  • Set aggressive caching cho static assets
  • Dùng ETagIf-None-Match
  • Set Access-Control-Max-Age để giảm preflight requests
  • Dùng Vary header đúng cách

Complete Example: Best Practice Request & Response

// CLIENT REQUEST
POST /api/orders HTTP/1.1
Host: api.example.com
User-Agent: MyApp/2.1.0 (iOS 17.0)
Accept: application/json
Accept-Language: vi-VN, en;q=0.9
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Request-ID: req-abc-123-xyz
X-Client-Version: 2.1.0
Origin: https://myapp.com

{
  "product_id": 101,
  "quantity": 2
}

// SERVER RESPONSE
HTTP/1.1 201 Created
Date: Mon, 15 Jan 2025 10:00:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 245
Content-Encoding: gzip
Cache-Control: private, no-cache
Location: /api/orders/789
ETag: "order-789-v1"
X-Request-ID: req-abc-123-xyz
X-Response-Time: 145ms

// Security Headers
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'

// CORS Headers
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: X-Request-ID, X-Response-Time
Vary: Origin

{
  "id": 789,
  "product_id": 101,
  "quantity": 2,
  "total": 500000,
  "status": "pending",
  "created_at": "2025-01-15T10:00:00Z"
}

Test Headers với Playwright

import { test, expect } from '@playwright/test';

test('Comprehensive headers test', async ({ request }) => {
  const requestId = `req-${Date.now()}`;

  const response = await request.post('https://api.example.com/orders', {
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'Accept-Language': 'vi-VN',
      'Accept-Encoding': 'gzip',
      'Authorization': 'Bearer test-token',
      'X-Request-ID': requestId,
      'X-Client-Version': '2.1.0'
    },
    data: {
      product_id: 101,
      quantity: 2
    }
  });

  // Verify status
  expect(response.status()).toBe(201);

  // Verify Content headers
  expect(response.headers()['content-type']).toContain('application/json');
  expect(response.headers()['content-length']).toBeTruthy();

  // Verify Security headers
  expect(response.headers()['strict-transport-security']).toBeTruthy();
  expect(response.headers()['x-content-type-options']).toBe('nosniff');
  expect(response.headers()['x-frame-options']).toBeTruthy();

  // Verify CORS headers
  expect(response.headers()['access-control-allow-origin']).toBeTruthy();

  // Verify custom headers
  expect(response.headers()['x-request-id']).toBe(requestId);

  // Verify body
  const data = await response.json();
  expect(data).toHaveProperty('id');
  expect(data.product_id).toBe(101);
});