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

Ảnh chụp đoạn mã nền tối minh hoạ ba kiểu xác thực HTTP Basic Bearer API key, Basic user pass Base64 trong header không phải mã hoá server chưa có credentials trả 401 kèm thử thách WWW-Authenticate Basic realm khu-vuc-noi-bo curl -u an matkhau-bi-mat basic curl tự đóng gói Authorization Basic base64 an matkhau-bi-mat base64 giải ngược được bắt buộc chạy trên HTTPS, Bearer gửi token đã cấp server tự kiểm chữ ký token bằng payload chấm chuky HMAC-SHA256 server ký khi phát kiểm chữ ký mỗi request sửa một ký tự là hỏng curl -H Authorization Bearer token bearer WWW-Authenticate Bearer error invalid_token khi sai, API key khoá tĩnh trong header riêng cho máy gọi máy curl -H X-API-Key k_live_9f2a7c apikey không phải scheme HTTP chuẩn nên thường không có WWW-Authenticate đơn giản nhưng khoá lộ là mất trắng nên giới hạn quyền và xoay khoá

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

Ảnh chụp bảng kết quả chạy thật python cộng curl output thật, Basic không gửi trả 401 thử thách curl -D - basic HTTP 401 Unauthorized WWW-Authenticate Basic realm khu-vuc-noi-bo charset UTF-8 curl -u an matkhau-bi-mat basic ok true chao an Authorization Basic YW46bWF0a2hhdS1iaS1tYXQ, Base64 giải ngược được lộ mật khẩu ngay echo YW46bWF0a2hhdS1iaS1tYXQ base64 -d ra an matkhau-bi-mat không mã hoá chỉ HTTPS mới an toàn, Bearer token đúng trả 200 ok true chu_the an at coffeecode.vn sửa một ký tự thêm X vào token HTTP 401 WWW-Authenticate Bearer error invalid_token, API key đúng trả 200 ok true ung_dung dich-vu-thanh-toan thiếu key status 401 không có thử thách

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ề

  1. Basic auth dùng Base64, KHÔNG phải mã hoá: đo thật, base64 -d giải ngược Authorization ra thẳng an: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ữ.
  2. 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.
  3. API key hợp cho máy gọi máy: khoá tĩnh trong header riêng (X-API-Key), 401 thườ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

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.