codejump

Protect FastAPI endpoints

Understand bearer token extraction, authentication dependencies, authorization checks, and safe error responses in FastAPI.

1 min readUpdated

3 exercises

Waiting at the end of this page.

Key takeaways

  • OAuth2PasswordBearer reads a bearer token from the Authorization header; it does not validate the token for you.
  • Authenticate the caller in a dependency, then check permissions for the requested resource.
  • Return 401 for missing or invalid credentials and 403 when an authenticated caller lacks permission.

Extract a bearer token

FastAPI's security utilities are dependencies. OAuth2PasswordBearer looks for Authorization: Bearer <token> and documents that scheme in OpenAPI:

python
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()
bearer = OAuth2PasswordBearer(tokenUrl="token")

def current_user(token: Annotated[str, Depends(bearer)]):
    user = verify_token_and_load_user(token)
    if user is None:
        raise HTTPException(status_code=401, detail="Invalid credentials")
    return user

tokenUrl tells clients where a token can be obtained; declaring it does not create that endpoint. The example's verify_token_and_load_user is application code you must implement. Merely extracting a token is not authentication: verify its integrity, expiry, intended audience, and associated user according to the token format your service uses.

Check authorization at the resource

Authentication identifies the caller; authorization decides whether that caller may perform the action. An authenticated user can still be forbidden from editing someone else's document:

python
@app.delete("/documents/{document_id}")
def delete_document(document_id: int, user: Annotated[User, Depends(current_user)]):
    document = load_document(document_id)
    if document is None:
        raise HTTPException(status_code=404, detail="Document not found")
    if document.owner_id != user.id:
        raise HTTPException(status_code=403, detail="Forbidden")
    remove_document(document)
    return {"deleted": True}

The names User, load_document, and remove_document stand for your application model and storage functions. The important boundary is that the permission check uses a trusted user loaded from verified credentials, never a user ID supplied by the client.

Keep credentials out of responses

Use a separate public response model for user data, hash passwords with a suitable password hashing library, and keep signing keys outside source code. Return a generic authentication error so a failed login does not reveal which account exists.

Now practice it

3 exercises

Reading this page is the cheap half. These are the exercises that make you use it.