Not every API needs username/password login or a full OAuth2 flow. If you only need to keep strangers out of an internal endpoint — an admin stats page, a webhook, or a route that triggers a background job — use HTTPBearer from fastapi.security: it reads the Authorization: Bearer <token> header, automatically rejects requests missing the header with a 401, and attaches to an endpoint through Depends() like any other dependency. The right token lets the request through; the wrong token stops at the API gate.
The Problem
Many people dig the header out by hand inside every 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}
This works, but it has to be repeated in every route: miss one spot and you leave a door open, and Swagger UI has no idea the endpoint needs a token, so it shows no Authorize button for testing.
The Solution
Hand header parsing to HTTPBearer and token checking to one shared dependency:
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 strips the token out of the header for you: a missing header or a wrong scheme — sending Token xxx instead of Bearer xxx, for example — gets an automatic 401 Not authenticated. One small note: Starlette 1.x returns 401, older versions returned 403, but both mean “not authenticated”. The verify_token dependency then has only one job: compare the token with secrets.compare_digest and hand it back to the endpoint. Attach Depends(verify_token) to any route and that route is protected, and Swagger UI picks up the Bearer scheme on its own, so the Authorize button appears for testing right in the docs.
For an endpoint that is both public and private, use HTTPBearer(auto_error=False): with no header, credentials is None instead of a 401, and the endpoint itself decides whether to return public data or internal data.
Apply It to Your Project
Put the token in an environment variable — never hardcode it. This approach fits internal admin endpoints best, webhooks receiving calls from outside services, routes that manually trigger a cron job, and APIs shared between internal services. When you need many users with different permissions, upgrade to JWT instead of one token shared by everyone.
Related Posts
- 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: Read Headers and Cookies with Header() and Cookie() in 5 Minutes
- FastAPI: Validate Path Parameters with Path() — Block Bad IDs at the API Gateway in 5 Minutes
Frequently Asked Questions
How is HTTPBearer different from OAuth2PasswordBearer?
HTTPBearer only checks whether the Bearer header carries a token; it does not care where the token came from. OAuth2PasswordBearer is tied to the OAuth2 password flow — a /token endpoint, a username/password form — and Swagger UI shows a login form for it. If you need full login, use OAuth2; if you only need one fixed token, HTTPBearer is far simpler.
Where should the token be stored?
In an environment variable or in your deployment infrastructure’s secret manager. Never commit a token to Git and never hardcode it in code. To rotate a token, change the environment variable and restart the service — no code change needed.
Why use secrets.compare_digest instead of ==?
The == operator stops comparing at the first differing character, so an attacker can measure response times to guess the token one character at a time. compare_digest compares in constant time and leaks nothing through timing.
Does a missing header return 401 or 403?
HTTPBearer returns 401 Not authenticated on Starlette 1.x (shipped with FastAPI 0.143); older Starlette versions returned 403. Both mean “not authenticated” — the client just needs to send the correct Authorization: Bearer <token> header to get through.
Can I use HTTPBearer for a public API?
Yes, with auto_error=False: a request without a token gets public data, a request with a valid token gets the extra private data. This fits endpoints that serve casual visitors and also offer more data to logged-in users.
References
- FastAPI docs — Security: https://fastapi.tiangolo.com/tutorial/security/
