Mở tab Network của trình duyệt, bạn thấy header Authorization: Basic YW46bWF0a2hhdS1iaS1tYXQ=. Chuỗi cuối trông "được mã hoá", nên nhiều người yên tâm rằng mật khẩu đã an toàn. Đó là hiểu lầm nguy hiểm nhất về xác thực HTTP: chuỗi đó là Base64, không phải mã hoá, và giải ngược được trong đúng một lệnh. Bài này (phần 15 loạt "HTTP và web đằng sau") dựng một server thật phục vụ cả ba scheme phổ biến — Basic, Bearer token, API key — rồi đo bằng curl để thấy mỗi cái bảo vệ được gì và không bảo vệ được gì.
HTTP xác thực bằng header, và cơ chế "thử thách"
Xác thực trong HTTP xoay quanh hai header: client gửi thông tin trong Authorization, còn server — khi thiếu hoặc sai — trả về 401 Unauthorized kèm WWW-Authenticate để thử thách (challenge), nói cho client biết cần loại xác thực nào. Đây là cơ chế chung; ba scheme dưới đây chỉ khác nhau ở nội dung của header Authorization.
Client --(request không kèm gì)--> Server
Client <--(401 + WWW-Authenticate: Basic ...)-- Server # thử thách
Client --(Authorization: <thông tin>)--> Server # đáp lại
Client <--(200 OK)-- Server

Hình 1: Ba scheme khác nhau ở nội dung header Authorization — Basic gói user:pass bằng Base64, Bearer gửi token đã ký, API key gửi khoá tĩnh; server thử thách bằng 401 + WWW-Authenticate.
Basic: Base64 là mã hoá dữ liệu, không phải bảo mật
Với Basic auth, client ghép user:pass, mã hoá Base64, rồi đặt vào Authorization: Basic <chuỗi>. curl -u làm giúp bạn việc này. Đo thật trên server chỉ chấp nhận an:matkhau-bi-mat:
$ curl -D - -o /dev/null http://127.0.0.1:8092/basic
HTTP/1.0 401 Unauthorized
WWW-Authenticate: Basic realm="khu-vuc-noi-bo", charset="UTF-8"
$ curl -u "an:matkhau-bi-mat" http://127.0.0.1:8092/basic
{"ok": true, "chao": "an"}
# Header curl gửi đi: Authorization: Basic YW46bWF0a2hhdS1iaS1tYXQ=
Điểm chết người: Base64 là phép mã hoá dữ liệu hai chiều, không dùng khoá, nên bất kỳ ai chặn được request đều giải ngược ra mật khẩu ngay lập tức:
$ echo "YW46bWF0a2hhdS1iaS1tYXQ=" | base64 -d
an:matkhau-bi-mat

Hình 2: Chạy thật — Basic không gửi → 401 + thử thách; đúng → 200; và base64 -d giải ngược chuỗi ra thẳng an:matkhau-bi-mat. Bearer token đúng → 200, sửa một ký tự → invalid_token. API key đúng → 200, thiếu → 401 không kèm thử thách.
Vì vậy Basic auth chỉ an toàn khi chạy trên HTTPS — khi đó cả header đã nằm trong đường hầm TLS. Trên HTTP trần, nó tương đương gửi mật khẩu dạng chữ.
Bearer: server giữ khoá, token tự chứng minh
Bearer token (RFC 6750) khác về bản chất: client không gửi mật khẩu mỗi lần, mà gửi một token đã được server cấp sau khi đăng nhập. Trong demo, token có dạng payload.chuky, với chuky là HMAC-SHA256 của payload bằng khoá bí mật chỉ server biết. Mỗi request, server tính lại chữ ký và so — nên không thể giả mạo token nếu không có khoá:
$ curl -H "Authorization: Bearer <token>" http://127.0.0.1:8092/bearer
{"ok": true, "chu_the": "an@coffeecode.vn"}
# Sửa đúng một ký tự cuối token:
$ curl -H "Authorization: Bearer <token>X" http://127.0.0.1:8092/bearer
HTTP/1.0 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token"
Chỉ đổi một ký tự, chữ ký không khớp, server trả 401 với error="invalid_token" — đúng bộ mã lỗi chuẩn của RFC 6750 (invalid_request, invalid_token, insufficient_scope). "Bearer" nghĩa là người mang: ai cầm token cũng dùng được, nên token phải giữ kín và nên có hạn dùng (đây chính là ý tưởng của JWT — chủ đề loạt Mật mã).
API key: đơn giản, cho máy gọi máy
API key là khoá tĩnh dài, thường đặt trong header riêng như X-API-Key (chính blog này dùng cách đó). Nó không phải scheme HTTP chuẩn, nên server thường trả 401 mà không kèm WWW-Authenticate:
$ curl -H "X-API-Key: k_live_9f2a7c" http://127.0.0.1:8092/apikey
{"ok": true, "ung_dung": "dich-vu-thanh-toan"}
$ curl http://127.0.0.1:8092/apikey
status=401
API key hợp cho giao tiếp máy-với-máy (backend gọi backend, webhook, script): không có bước đăng nhập tương tác, chỉ một khoá cắm sẵn trong cấu hình. Đổi lại, khoá là bí mật lâu dài — lộ là mất trắng cho tới khi bị xoay.
Đánh đổi cần cân nhắc
Mọi scheme đều vô nghĩa nếu không có HTTPS. Basic lộ mật khẩu, Bearer và API key lộ token/khoá — tất cả đều nằm chữ trong header. TLS không phải "thêm cho chắc" mà là điều kiện cần của xác thực HTTP. Blog này ép HTTPS bằng HSTS ở tầng ứng dụng chính vì lẽ đó.
Bearer và API key nên có quyền hạn hẹp và vòng đời. Token nên hết hạn và cấp lại được (refresh); API key nên giới hạn phạm vi (chỉ đọc, chỉ một dịch vụ) và xoay định kỳ. Một khoá "toàn quyền, vĩnh viễn" biến mọi rò rỉ log thành sự cố nghiêm trọng — đúng bài học "không ghi bí mật ra log".
401 khác 403. 401 Unauthorized nghĩa là chưa xác thực (thiếu/sai credentials) — đi kèm thử thách để client thử lại. 403 Forbidden nghĩa là đã biết bạn là ai nhưng không đủ quyền — thử lại cùng credentials cũng vô ích. Trả nhầm mã khiến client xử lý sai (hỏi lại mật khẩu trong khi vấn đề là phân quyền).
Ba ý mang về
- Basic auth dùng Base64, KHÔNG phải mã hoá: đo thật,
base64 -dgiải ngượcAuthorizationra thẳngan:matkhau-bi-mat. Nó chỉ an toàn khi chạy trên HTTPS — trên HTTP trần là gửi mật khẩu dạng chữ. - Bearer token tự chứng minh bằng chữ ký server giữ khoá: đo thật, token đúng →
200, sửa một ký tự →401 error="invalid_token". Không cần gửi mật khẩu mỗi lần, nhưng "ai mang cũng dùng được" nên phải giữ kín và có hạn. - API key hợp cho máy gọi máy: khoá tĩnh trong header riêng (
X-API-Key),401thường không kèm thử thách vì không phải scheme chuẩn — cần giới hạn quyền và xoay khoá vì nó là bí mật lâu dài.
Nguồn
- MDN Web Docs — HTTP authentication: https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication
- RFC 7617 — The 'Basic' HTTP Authentication Scheme: https://datatracker.ietf.org/doc/html/rfc7617
- RFC 6750 — The OAuth 2.0 Authorization Framework: Bearer Token Usage: https://datatracker.ietf.org/doc/html/rfc6750
Phần sau ta xem cách tải một phần tài nguyên: header Range và Accept-Ranges, mã 206 Partial Content — nền tảng của tua video, tải tiếp file dở và tải song song nhiều đoạn.