Không phải API nào cũng cần đăng nhập bằng username/password hay luồng OAuth2 đầy đủ. Nếu bạn chỉ cần chặn người lạ truy cập một endpoint nội bộ — ví dụ trang thống kê admin, webhook, hay route kích hoạt tác vụ nền — hãy dùng HTTPBearer trong fastapi.security: nó đọc header Authorization: Bearer <token>, tự chặn request thiếu header bằng lỗi 401, và gắn vào endpoint qua Depends() như mọi dependency khác. Token đúng thì request đi tiếp, token sai thì dừng ngay từ cổng API.
Vấn đề
Nhiều người tự moi header trong từng endpoint:
@app.get("/admin/stats")def admin_stats(request: Request): auth = request.headers.get("Authorization", "") if not auth.startswith("Bearer "): raise HTTPException(status_code=401, detail="Thieu token") if auth.split(" ", 1)[1] != API_TOKEN: raise HTTPException(status_code=401, detail="Token khong hop le") return {"endpoint": "admin/stats", "token_ok": True}
Cách này chạy được nhưng phải lặp lại ở mọi route: quên một chỗ là hở một cửa, và Swagger UI cũng không biết endpoint cần token nên không hiện nút Authorize để test.
Giải pháp
Đưa việc đọc header cho HTTPBearer, việc kiểm tra token cho một dependency dùng chung:
import osimport secretsfrom fastapi import Depends, FastAPI, HTTPExceptionfrom fastapi.security import HTTPAuthorizationCredentials, HTTPBearerAPI_TOKEN = os.environ["API_TOKEN"]security = HTTPBearer()app = FastAPI()def verify_token( credentials: HTTPAuthorizationCredentials = Depends(security),) -> str: if not secrets.compare_digest(credentials.credentials, API_TOKEN): raise HTTPException(status_code=401, detail="Token khong hop le") return credentials.credentials@app.get("/admin/stats")def admin_stats(token: str = Depends(verify_token)): return {"endpoint": "admin/stats", "token_ok": True}
HTTPBearer tách sẵn token khỏi header: thiếu header hoặc sai scheme — ví dụ gửi Token xxx thay vì Bearer xxx — thì tự trả 401 Not authenticated. Lưu ý nhỏ: Starlette 1.x trả 401, các bản cũ trả 403, nhưng ý nghĩa đều là “chưa xác thực”. Dependency verify_token chỉ còn một việc: so token bằng secrets.compare_digest rồi trả token về cho endpoint dùng. Gắn Depends(verify_token) vào route nào thì route đó được bảo vệ, và Swagger UI tự nhận scheme Bearer nên hiện nút Authorize để test ngay trên docs.
Muốn endpoint vừa công khai vừa riêng tư, dùng HTTPBearer(auto_error=False): thiếu header thì credentials là None thay vì lỗi 401, endpoint tự quyết định trả dữ liệu công khai hay nội bộ.
Áp dụng vào dự án của bạn
Đặt token vào biến môi trường, đừng hardcode trong code. Cách này hợp nhất cho endpoint admin nội bộ, webhook nhận từ dịch vụ bên ngoài, route kích hoạt cron thủ công, và API dùng chung giữa các service nội bộ. Khi cần nhiều user với quyền khác nhau, hãy nâng cấp lên JWT thay vì một token dùng chung cho mọi người.
Bài viết liên quan
- FastAPI: bảo vệ route bằng dependency JWT/OAuth2 tái sử dụng, không lặp code xác thực
- FastAPI: đọc header và cookie bằng Header() và Cookie() trong 5 phút — khỏi moi request thủ công
- FastAPI: validate tham số đường dẫn bằng Path() — chặn ID sai ngay từ cổng API trong 5 phút
Câu hỏi thường gặp
HTTPBearer khác gì OAuth2PasswordBearer?
HTTPBearer chỉ kiểm tra header Bearer có token hay không, không quan tâm token lấy từ đâu. OAuth2PasswordBearer gắn với luồng OAuth2 password flow — endpoint /token, form username/password — và Swagger UI hiện form đăng nhập. Cần login đầy đủ thì dùng OAuth2, chỉ cần một token cố định thì HTTPBearer gọn hơn nhiều.
Token nên lưu ở đâu?
Lưu trong biến môi trường hoặc secret manager của hạ tầng deploy. Không commit token vào Git, không hardcode trong code. Khi cần xoay token, đổi biến môi trường và restart service là xong, không phải sửa code.
Vì sao dùng secrets.compare_digest thay vì ==?
Toán tử == dừng so sánh ở ký tự đầu tiên khác nhau, nên kẻ tấn công có thể đo thời gian phản hồi để đoán dần từng ký tự của token. compare_digest so sánh trong thời gian hằng định, không rò rỉ thông tin qua thời gian.
Thiếu header thì trả 401 hay 403?
HTTPBearer trả 401 Not authenticated trên Starlette 1.x (đi kèm FastAPI 0.143); các bản Starlette cũ trả 403. Cả hai đều có nghĩa “chưa xác thực”, client chỉ cần gửi đúng header Authorization: Bearer <token> là qua được cổng.
Có dùng HTTPBearer cho API công khai được không?
Được, với auto_error=False: request không token thì trả dữ liệu công khai, request có token hợp lệ thì trả thêm phần riêng tư. Cách này phù hợp cho endpoint vừa phục vụ khách vãng lai vừa mở rộng thêm dữ liệu cho user đã đăng nhập.
💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.
Nguồn tham khảo
- Tài liệu FastAPI — Security: https://fastapi.tiangolo.com/tutorial/security/
