API Key (Application Programming Interface Key) là một chuỗi ký tự định danh duy nhất (Unique Code) được cấp cho ứng dụng hoặc người dùng để xác thực và ủy quyền khi truy cập vào một dịch vụ Web/API.
Bạn có thể hình dung API Key giống như một chiếc chìa khóa cửa hoặc thẻ hội viên:
Xác thực (Authentication): Cho máy chủ (Server) biết AI đang gửi yêu cầu (định danh ứng dụng/người dùng).
Kiểm soát truy cập (Authorization): Xác định xem khóa đó có quyền gọi các tính năng nào và giới hạn hạn mức sử dụng (Rate Limit - ví dụ: chỉ cho phép gọi 1.000 request/ngày).
Theo dõi & Theo dõi chi phí (Accounting & Billing): Dùng để tính phí sử dụng (như OpenAI API, Google Maps API) hoặc phát hiện lạm dụng.
| Tiêu chí | API Key | JWT (JSON Web Token) / Session |
| Định danh cho | Ứng dụng / Dự án (Project/App) | Người dùng cụ thể (User) |
| Mục đích chính | Xác thực ứng dụng nào đang gọi API | Xác thực người dùng nào đang đăng nhập |
| Thời hạn | Thường là cố định (Lâu dài) | Hết hạn nhanh (ngắn hạn: 15 phút - vài ngày) |
| Độ bảo mật | Trung bình (Nên giấu ở Backend) | Cao (Chứa thông tin xác thực động) |
API Key bản chất chỉ là một chuỗi ký tự bí mật (hoặc một mã định danh) mà client gửi kèm theo mỗi request để chứng minh ứng dụng đó có quyền truy cập vào dịch vụ của server.
API Key KHÔNG có cơ chế mã hóa hay chữ ký số bên trong như JWT.
Dưới đây là sự khác biệt bản chất giữa hai khái niệm này:
| Tiêu chí | API Key | JWT (JSON Web Token) |
| Cấu trúc bên trong | Là một chuỗi ngẫu nhiên vô nghĩa (Opague String), ví dụ: sk_live_abc123xyz... |
Cấu trúc 3 phần có nghĩa (Header.Payload.Signature) chứa dữ liệu định dạng JSON. |
| Chữ ký số (Signature) | Không có. Bản thân chuỗi API Key không chứa chữ ký số nào. | Có. Phần Signature được băm/ký bằng thuật toán HMAC-SHA256 hoặc RSA với Secret Key trên Server. |
| Cơ chế kiểm tra | Stateful (Tra cứu DB): Khi nhận API Key, Server bắt buộc phải tra vào Database/Redis xem Key này là của ai, có hợp lệ không. | Stateless (Tự xác thực): Server chỉ cần tính toán lại Chữ ký số. Nếu chữ ký hợp lệ, Server tin tưởng dữ liệu bên trong Payload mà không cần tra Database. |
| Thông tin mang theo | Không mang theo thông tin ngữ cảnh. Server phải tự tra cứu thông tin đi kèm từ DB. | Tự mang theo dữ liệu (User ID, Role, thời gian hết hạn exp,...) ngay trong phần Payload. |
JWT cần chữ ký số vì thông tin người dùng (Payload) nằm công khai dưới dạng Base64 ở phía Client. Nếu không có Chữ ký số (Signature) ở phần 3, người dùng hay hacker có thể mở ra sửa user_id: 10 thành user_id: 1 (Admin) rồi gửi lại cho Server. Chữ ký số giúp Server phát hiện dữ liệu Payload đã bị can thiệp.
API Key không cần chữ ký số vì nó giống như một "chiếc chìa khóa vật lý" hoặc "mã PIN". Bản thân chuỗi ký tự đó không chứa dữ liệu gì để sửa đổi cả. Sự bảo mật của API Key phụ thuộc hoàn toàn vào:
Độ dài và độ ngẫu nhiên của chuỗi (để chống đoán mò / brute-force).
Bắt buộc truyền qua đường dẫn mã hóa HTTPS (SSL/TLS) để không bị bắt lén trên đường truyền.
Dùng API Key: Khi bạn cấp quyền cho một ứng dụng/hệ thống bên thứ 3 kết nối vào hệ thống của bạn (Ví dụ: SePay cấp API Key cho Flask App của anh, hoặc Google Cloud cấp API Key cho ứng dụng đọc Google Maps).
Dùng JWT: Khi bạn muốn quản lý phiên làm việc của người dùng cuối (End-user) đăng nhập vào ứng dụng Web/Mobile (cần lưu role, thông tin đăng nhập tạm thời, thời hạn hết hạn token tự động mà không làm tải Database).
import hashlib
import hmac
import secrets
from typing import Dict, Optional
# =====================================================================
# 1. PHÍA SERVER: Cấu hình và Quản lý API Key
# =====================================================================
class APIServer:
def __init__(self):
# Cơ sở dữ liệu giả lập lưu trữ các API Key hợp lệ và thông tin giới hạn
# Trong thực tế, dữ liệu này lưu trong Database (Redis, PostgreSQL...)
self.api_keys_db: Dict[str, dict] = {}
def generate_api_key(self, client_name: str, rate_limit: int = 100) -> str:
"""Tạo một API Key ngẫu nhiên an toàn cho khách hàng"""
# Tạo chuỗi ngẫu nhiên 32 bytes (64 ký tự hex)
raw_key = f"sk_live_{secrets.token_hex(24)}"
# Lưu thông tin client vào DB
self.api_keys_db[raw_key] = {
"client_name": client_name,
"rate_limit_per_day": rate_limit,
"used_today": 0,
"is_active": True,
}
return raw_key
def verify_request(
self, api_key: Optional[str], endpoint: str
) -> tuple[int, dict]:
"""Xác thực API Key gửi lên từ Client"""
# Kiểm tra xem có gửi Key hay không
if not api_key:
return 401, {
"error": "Unauthorized",
"message": "Thiếu API Key trong Header 'X-API-Key'",
}
# Kiểm tra Key có tồn tại trong hệ thống không
client_info = self.api_keys_db.get(api_key)
if not client_info or not client_info["is_active"]:
return 403, {
"error": "Forbidden",
"message": "API Key không hợp lệ hoặc đã bị khóa",
}
# Kiểm tra hạn mức sử dụng (Rate Limit)
if client_info["used_today"] >= client_info["rate_limit_per_day"]:
return 429, {
"error": "Too Many Requests",
"message": "Đã vượt quá hạn mức truy cập trong ngày",
}
# Cập nhật số lần sử dụng
client_info["used_today"] += 1
# Trả về dữ liệu thành công
return 200, {
"status": "success",
"message": f"Truy cập endpoint '{endpoint}' thành công!",
"client": client_info["client_name"],
"requests_left": client_info["rate_limit_per_day"]
- client_info["used_today"],
}
# =====================================================================
# 2. PHÍA CLIENT: Gửi Yêu cầu kèm API Key
# =====================================================================
class APIClient:
def __init__(self, api_key: Optional[str] = None):
self.api_key = api_key
def make_request(self, server: APIServer, endpoint: str):
"""Mô phỏng gửi HTTP Request với Header 'X-API-Key'"""
# Giả lập Header HTTP
headers = {}
if self.api_key:
headers["X-API-Key"] = self.api_key
# Gọi Server xác thực
status_code, response = server.verify_request(
headers.get("X-API-Key"), endpoint
)
print(f"[HTTP Status {status_code}] -> {response}")
# =====================================================================
# 3. KỊCH BẢN THỬ NGHIỆM (RUN DEMO)
# =====================================================================
if __name__ == "__main__":
# BƯỚC 1: Khởi tạo Server
server = APIServer()
# BƯỚC 2: Cấp API Key cho ứng dụng di động (Cho phép tối đa 2 request/ngày để test)
my_api_key = server.generate_api_key(
client_name="Flutter_Mobile_App", rate_limit=2
)
print(f"🔑 API Key vừa cấp: {my_api_key}\n")
print("--- THỜI DIỂM 1: Client KHÔNG gửi API Key ---")
client_no_key = APIClient()
client_no_key.make_request(server, "/api/v1/courses")
print("\n--- THỜI DIỂM 2: Client gửi API Key SAI/GIA TẠO ---")
client_bad_key = APIClient(api_key="sk_live_12345_fake_key")
client_bad_key.make_request(server, "/api/v1/courses")
print("\n--- THỜI DIỂM 3: Client gửi API Key HỢP LỆ (Request 1 & 2) ---")
client_valid = APIClient(api_key=my_api_key)
client_valid.make_request(server, "/api/v1/courses")
client_valid.make_request(server, "/api/v1/user/profile")
print("\n--- THỜI DIỂM 4: Client gọi tiếp (Vượt quá Rate Limit = 2) ---")
client_valid.make_request(server, "/api/v1/reports")
Màn hình Kết quả khi chạy chương trình:
🔑 API Key vừa cấp: sk_live_8f3a1e9b2c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d
--- THỜI DIỂM 1: Client KHÔNG gửi API Key ---
[HTTP Status 401] -> {'error': 'Unauthorized', 'message': "Thiếu API Key trong Header 'X-API-Key'"}
--- THỜI DIỂM 2: Client gửi API Key SAI/GIA TẠO ---
[HTTP Status 403] -> {'error': 'Forbidden', 'message': 'API Key không hợp lệ hoặc đã bị khóa'}
--- THỜI DIỂM 3: Client gửi API Key HỢP LỆ (Request 1 & 2) ---
[HTTP Status 200] -> {'status': 'success', 'message': "Truy cập endpoint '/api/v1/courses' thành công!", 'client': 'Flutter_Mobile_App', 'requests_left': 1}
[HTTP Status 200] -> {'status': 'success', 'message': "Truy cập endpoint '/api/v1/user/profile' thành công!", 'client': 'Flutter_Mobile_App', 'requests_left': 0}
--- THỜI DIỂM 4: Client gọi tiếp (Vượt quá Rate Limit = 2) ---
[HTTP Status 429] -> {'error': 'Too Many Requests', 'message': 'Đã vượt quá hạn mức truy cập trong ngày'}
Không lưu trực tiếp trên Git / Frontend: Luôn đưa API Key vào biến môi trường (.env) ở phía Backend (Server-side).
Ủy quyền qua HTTP Header: Nên gửi API Key qua Header (như X-API-Key hoặc Authorization: Bearer <key>) thay vì đặt trực tiếp trên đường dẫn URL (như ?api_key=xyz), vì URL dễ bị lưu lại trong lịch sử trình duyệt hoặc nhật ký truy cập (Log) của Server.
Chức năng thu hồi (Revoke): Hệ thống cần có cơ chế cho phép vô hiệu hóa (deactivate) một API Key ngay lập tức nếu phát hiện bị rò rỉ.