Mở đầu — vì sao bài này quan trọng
Hãy hình dung bạn là Technical BA của một ví điện tử, và đối tác là một chuỗi siêu thị muốn tích hợp thanh toán. Cuộc họp kỹ thuật đầu tiên, bên đối tác hỏi thẳng: "API của bạn xác thực thế nào? Dùng API Key hay OAuth2? Token sống bao lâu? Bên tôi gọi server-to-server thì phải làm sao?". Nếu lúc đó bạn ngơ ngác, uy tín của cả nhóm tụt xuống ngay lập tức, và tệ hơn, bạn sẽ viết ra một bản API spec mơ hồ khiến developer hai bên hiểu sai nhau, dẫn đến lỗ hổng bảo mật hoặc trễ tích hợp hàng tuần.
Authentication (xác thực — "bạn là ai?") là phần xương sống của bất kỳ API nào. Là Technical BA, bạn không cần tự code phần đăng nhập, nhưng bạn BẮT BUỘC phải hiểu đủ sâu để: chọn đúng cơ chế cho từng use case, viết spec rõ ràng cho developer, đặt câu hỏi đúng với đối tác, và lường trước rủi ro bảo mật trước khi nó trở thành sự cố. Bài này tập trung vào ba cơ chế xác thực phổ biến nhất mà bạn sẽ gặp đi gặp lại trong 90% dự án: API Key, OAuth2 và JWT. Chúng ta sẽ làm rõ chúng là gì, khác nhau ra sao, và quan trọng nhất — khi nào chọn cái nào.
Lưu ý nhỏ: bài này nói về Authentication (xác thực danh tính). Phân biệt rạch ròi giữa Authentication và Authorization (phân quyền — "bạn được làm gì?") sẽ là chủ đề riêng ở các bài sau, nên ở đây ta chỉ chạm nhẹ khi cần.
Khái niệm cốt lõi
1. API Key — tấm thẻ ra vào đơn giản nhất
API Key là một chuỗi ký tự ngẫu nhiên, dài và khó đoán (ví dụ sk_live_8fK2mNp9XqR4tY7wZ...) mà hệ thống cấp cho một client. Mỗi lần gọi API, client đính kèm key này — thường trong HTTP header (Authorization: Bearer <key> hoặc X-API-Key: <key>) hoặc đôi khi trong query string (cách này kém an toàn, không nên dùng).
Bản chất API Key chỉ trả lời được câu hỏi "request này đến từ ai/ứng dụng nào?". Nó không gắn với một người dùng cuối cụ thể, không tự hết hạn (trừ khi bạn chủ động thu hồi), và không mang theo thông tin gì ngoài chính nó. Server nhận key xong sẽ tra cứu trong database xem key này thuộc về ai, còn hiệu lực không, được gọi những endpoint nào.
Use case điển hình: giao tiếp server-to-server, partner API, dịch vụ nội bộ. Ví dụ bạn tích hợp Google Maps, OpenAI, hay một cổng SMS — họ cấp cho backend của bạn một API Key. Đây là kiểu "máy nói chuyện với máy", không có con người ngồi đăng nhập từng lần.
Điểm yếu cốt tử: API Key giống như một chiếc chìa khóa vạn năng. Ai cầm được nó là vào được, không cần biết người đó là ai. Nếu key lộ ra (lập trình viên vô tình commit lên GitHub, lưu trong file cấu hình bị rò rỉ), kẻ xấu dùng được ngay. Vì thế API Key tuyệt đối không được đặt ở phía client mà người dùng cuối nhìn thấy (như JavaScript trên trình duyệt hay app mobile đã giải nén).
2. OAuth2 — giao thức ủy quyền cho thế giới thực
OAuth2 không phải là một "token", mà là một framework/giao thức mô tả cách một ứng dụng xin quyền truy cập tài nguyên thay mặt người dùng, mà không cần người dùng đưa mật khẩu cho ứng dụng đó.
Hãy nhớ tình huống quen thuộc: bạn vào một app mới và thấy nút "Đăng nhập bằng Google". Bạn bấm vào, Google hiện màn hình hỏi "App XYZ muốn truy cập email và tên của bạn, đồng ý không?". Bạn bấm Đồng ý, và app được vào — nhưng app không bao giờ biết mật khẩu Google của bạn. Đó chính là OAuth2 đang chạy.
Các vai trò trong OAuth2 (thuật ngữ bạn sẽ thấy trong spec):
- Resource Owner: người dùng cuối (chủ sở hữu dữ liệu).
- Client: ứng dụng muốn truy cập dữ liệu (app XYZ).
- Authorization Server: nơi cấp token sau khi xác thực (Google).
- Resource Server: nơi giữ dữ liệu thật (API của Google).
OAuth2 có nhiều "luồng" (grant types). Hai luồng BA cần phân biệt:
- Authorization Code Flow (kèm PKCE cho mobile/SPA): dùng khi có người dùng cuối thật ngồi bấm "đồng ý". Đây là luồng chuẩn cho web app, mobile app.
- Client Credentials Flow: dùng khi không có người dùng, chỉ là backend gọi backend. Về cảm giác nó hơi giống API Key nhưng an toàn hơn vì token có hạn và scope rõ ràng.
3. JWT — định dạng token "biết tự nói về mình"
JWT (JSON Web Token) là một định dạng token, không phải một giao thức. Nó thường được dùng bên trong OAuth2 (Access Token thường chính là một JWT). JWT là một chuỗi gồm ba phần ngăn cách bởi dấu chấm: header.payload.signature.
- Header: thuật toán ký (ví dụ HS256, RS256).
- Payload (claims): dữ liệu nhúng bên trong — ai (
sub), hết hạn lúc nào (exp), quyền gì, phát hành bởi ai (iss). - Signature: chữ ký số đảm bảo token không bị sửa.
Nhưng "tự chứa" cũng có hai cái bẫy mà BA phải ghi vào spec:
- JWT không mã hóa nội dung (chỉ ký, không giấu). Phần payload chỉ được mã hóa Base64, ai cũng giải ra đọc được. Tuyệt đối không nhét mật khẩu, số thẻ, số CCCD vào payload.
- JWT rất khó thu hồi trước hạn. Vì server không tra database, một token đã phát hành sẽ hợp lệ cho đến khi hết hạn (
exp), kể cả khi bạn muốn "đá" người dùng ra ngay. Đây là lý doexpnên ngắn, và cần cơ chế blacklist hoặc token version nếu nghiệp vụ đòi hỏi đăng xuất tức thì.
Bảng so sánh nhanh
| Tiêu chí | API Key | OAuth2 | JWT |
|---|---|---|---|
| Bản chất | Chuỗi bí mật cố định | Giao thức ủy quyền | Định dạng token |
| Gắn với người dùng? | Không (gắn app) | Có (resource owner) | Tùy claims (thường có) |
| Hết hạn? | Không, phải thu hồi tay | Có (access token ngắn hạn) | Có (exp) |
| Use case chính | Server-to-server, partner | Đăng nhập user, ủy quyền bên thứ ba | Token nội bộ trong OAuth2, session API |
| Thu hồi tức thì | Dễ (xóa key) | Trung bình (refresh token) | Khó (cần blacklist) |
Tình huống thực tế
Tình huống 1: VNPay tích hợp với sàn thương mại điện tử (Client Credentials + API Key)
Một sàn TMĐT Việt Nam giả định tên "ChợViệt" cần tích hợp cổng thanh toán kiểu VNPay. Đây là giao tiếp server-to-server: backend của ChợViệt gọi sang backend cổng thanh toán để tạo giao dịch, không có người dùng cuối trực tiếp bấm "đồng ý" trong luồng API.
Đội tích hợp ban đầu định dùng một API Key đơn giản. Nhưng Technical BA đặt câu hỏi: "Nếu key này lộ, kẻ xấu có tạo được lệnh thanh toán giả không?". Câu trả lời là có — quá rủi ro với tiền. Vì vậy spec được điều chỉnh: dùng OAuth2 Client Credentials Flow để backend ChợViệt xin một access token ngắn hạn (sống 5 phút), và mỗi request thanh toán phải kèm thêm chữ ký HMAC trên nội dung giao dịch bằng một secret riêng. Cách này khiến cho dù token bị nghe lén, kẻ xấu cũng không giả mạo được nội dung giao dịch.
Bài học rút ra: với luồng tiền, đừng dừng ở một lớp. BA cần phân biệt rõ "xác thực client" (token) và "đảm bảo toàn vẹn nội dung" (chữ ký). Và token càng nhạy cảm thì exp càng phải ngắn.
Tình huống 2: Super app gọi vốn kiểu Grab — đăng nhập bằng OAuth2 + JWT
Một super app khu vực Đông Nam Á (kiểu Grab/Gojek) có hàng chục microservice: đặt xe, giao đồ ăn, ví tiền, điểm thưởng. Khi người dùng đăng nhập một lần, họ phải dùng được tất cả dịch vụ mà không đăng nhập lại từng cái.
Giải pháp: người dùng đăng nhập qua Authorization Code Flow + PKCE (vì là mobile app). Authorization Server phát hành một Access Token dạng JWT (sống 15 phút) chứa userId và các scope, cùng một Refresh Token (sống 30 ngày). Mỗi microservice nhận JWT chỉ cần verify chữ ký bằng public key là biết người dùng là ai — không service nào phải gọi về service "đăng nhập" để hỏi, nên hệ thống chịu được hàng triệu request.
Một sự cố thật từng xảy ra ở các hệ thống kiểu này: khi một nhân viên bị nghỉ việc, tài khoản nội bộ của họ cần bị khóa ngay. Nhưng vì JWT sống 15 phút và stateless, họ vẫn dùng được trong 15 phút sau khi bị khóa. Đội phải thêm cơ chế blacklist token cho các hành động nhạy cảm.
Bài học rút ra: JWT stateless là con dao hai lưỡi — nhanh và dễ mở rộng, nhưng đăng xuất/khóa tức thì là điểm yếu. BA phải hỏi nghiệp vụ: "Có yêu cầu vô hiệu hóa phiên ngay lập tức không?". Nếu có, phải thiết kế thêm, đừng để developer tự ý bỏ qua.
Tình huống 3: Đối tác fintech và API Key bị commit lên GitHub
Một startup fintech tại TP.HCM cấp API Key cho 20 đối tác doanh nghiệp gọi API tra cứu điểm tín dụng. Một đối tác vô tình đẩy key lên một repo GitHub public. Trong 6 giờ, bot quét GitHub đã tìm ra key và gọi hơn 40.000 request, gây tốn chi phí và rò rỉ dữ liệu tra cứu.
Sau sự cố, Technical BA viết lại spec cấp key với ba nguyên tắc: (1) mỗi đối tác có key riêng để thu hồi độc lập, không ảnh hưởng người khác; (2) key phải có thể rotate (xoay vòng) định kỳ và hỗ trợ hai key song song trong giai đoạn chuyển tiếp để không downtime; (3) gắn IP allowlist — key chỉ dùng được từ dải IP đã đăng ký.
Bài học rút ra: API Key tiện nhưng "trần trụi". Là BA, đừng chỉ viết "dùng API Key" trong spec rồi xong. Phải đặc tả luôn vòng đời của key: cấp, xoay vòng, thu hồi, giới hạn phạm vi (IP, scope, rate limit). Chính những chi tiết này phân biệt một spec nghiệp dư với một spec chuyên nghiệp.
Hướng dẫn từng bước
Khi bạn đứng trước một dự án cần đặc tả authentication, hãy đi theo trình tự sau:
- Xác định "ai gọi ai". Có người dùng cuối thật trong luồng không? Nếu là máy-gọi-máy (backend-to-backend) → nghiêng về API Key hoặc OAuth2 Client Credentials. Nếu có người dùng cần ủy quyền (login, "đăng nhập bằng Google") → OAuth2 Authorization Code Flow.
- Đánh giá độ nhạy cảm của dữ liệu/hành động. Đọc dữ liệu công khai → API Key đơn giản đủ dùng. Thao tác tiền bạc, dữ liệu cá nhân → OAuth2 + token ngắn hạn + chữ ký nội dung.
- Chọn định dạng token và thời hạn. Quyết định access token có phải JWT không. Đặt
exphợp lý: thường 15 phút cho access token, vài tuần cho refresh token. Ghi rõ con số này trong spec, đừng để mơ hồ.
- Đặc tả nơi đặt token và cách truyền. Header nào (
Authorization: Bearer ...), qua HTTPS bắt buộc. Cấm tuyệt đối truyền token qua query string.
- Đặc tả vòng đời. Cấp như thế nào, làm mới (refresh) ra sao, thu hồi/đăng xuất thế nào, xử lý token hết hạn (trả về HTTP 401 với thông điệp lỗi chuẩn).
- Liệt kê tình huống lỗi. Token sai, token hết hạn, token thiếu scope, key bị thu hồi — mỗi trường hợp trả về mã lỗi gì. Đây là phần developer rất cần và BA hay quên.
- Rà soát bảo mật. Token có chứa thông tin nhạy cảm không? Có IP allowlist không? Có rate limit không? Có log lại các lần xác thực thất bại để phát hiện tấn công không?
Lỗi thường gặp & mẹo
Lỗi 1: Nhầm Authentication với Authorization. Token hợp lệ chỉ chứng minh "bạn là ai", không tự động cho phép "bạn được làm gì". BA hay viết spec kiểu "có token là gọi được mọi endpoint" — sai. Phải tách bạch scope/quyền riêng.
Lỗi 2: Nhét dữ liệu nhạy cảm vào JWT payload. Vì payload chỉ là Base64, ai cũng đọc được. Đừng bao giờ để mật khẩu, số thẻ, CCCD trong đó. Mẹo: tự dán một JWT mẫu vào trang jwt.io để thấy payload "trần trụi" thế nào — bạn sẽ nhớ mãi.
Lỗi 3: Đặt exp quá dài hoặc không đặt. Token sống 30 ngày mà lộ ra là thảm họa. Mẹo: access token ngắn (phút), refresh token dài hơn nhưng có thể thu hồi.
Lỗi 4: Quên luồng "hết hạn rồi làm gì". Spec chỉ nói lúc thành công, không nói khi token hết hạn client phải refresh ra sao. Hệ quả là developer hai bên tự đoán, sinh bug. Luôn vẽ cả luồng refresh.
Lỗi 5: Coi API Key là an toàn tuyệt đối. Nó là chìa khóa trần. Mẹo: luôn đặc tả rotate + thu hồi + giới hạn (IP, scope, rate limit) cho mọi API Key.
Mẹo vàng: mọi cơ chế trên chỉ an toàn khi đi qua HTTPS. Token truyền qua HTTP thường có thể bị nghe lén. Đây là điều kiện tiên quyết, ghi vào ngay dòng đầu phần security của spec.
Bài tập thực hành
- Phân loại use case: Cho ba tình huống sau, hãy chọn cơ chế phù hợp (API Key / OAuth2 Auth Code / OAuth2 Client Credentials) và giải thích lý do:
- Soi JWT: Truy cập jwt.io, dán một JWT mẫu bất kỳ, và viết ra ba claim bạn thấy trong payload. Giải thích
subvàexpnghĩa là gì, và vì sao không nên để số CCCD trong đó.
- Viết mini-spec: Soạn một đoạn spec ngắn (khoảng 1 trang) cho endpoint
POST /paymentscủa một cổng thanh toán: cơ chế xác thực, header truyền token, thời hạn token, và ít nhất ba mã lỗi xác thực (token thiếu, token hết hạn, token sai chữ ký) kèm HTTP status tương ứng.
- Phản biện rủi ro: Đối tác đề xuất dùng một API Key duy nhất chung cho cả 50 khách hàng để "cho gọn". Viết ba lý do bạn — với vai trò Technical BA — sẽ phản đối, và đề xuất thay thế.
Tóm tắt
- Authentication trả lời "bạn là ai?", là nền tảng bảo mật của mọi API mà Technical BA phải đặc tả rõ ràng.
- API Key: chuỗi bí mật cố định, hợp với server-to-server và partner API. Đơn giản nhưng "trần trụi" — phải đặc tả vòng đời (rotate, thu hồi, giới hạn IP/scope).
- OAuth2: giao thức ủy quyền, hợp khi có người dùng cuối (login, "đăng nhập bằng Google") hoặc backend-to-backend qua Client Credentials. Cho token ngắn hạn + refresh token.
- JWT: định dạng token tự chứa, thường dùng bên trong OAuth2, rất hợp microservices vì stateless. Nhưng không mã hóa nội dung và khó thu hồi tức thì — cân nhắc khi nghiệp vụ cần đăng xuất ngay.
- Quy tắc chọn: xác định "ai gọi ai" và độ nhạy cảm dữ liệu trước, rồi mới chọn cơ chế. Luôn HTTPS, luôn đặc tả luồng lỗi và luồng refresh.