Tutorial

FastAPI Auth: The Security Mental Model You Need First

Authentication and authorization are two different problems that most tutorials conflate. Here is the mental model, threat landscape, and FastAPI security toolkit you need before writing a single line of auth code.

Tin Dang avatar
Tin Dang
Split-view diagram showing a bouncer checking IDs on the left and a VIP list on the right

You open a FastAPI tutorial, search for “authentication,” and find a code block that hashes a password, issues a JWT, and checks a role — all in a single function. You copy it, it works, and three months later you are debugging a privilege escalation bug at 1 AM because the tutorial conflated three different security concepts into 40 lines.

Authentication and authorization are different problems with different threat models, different failure modes, and different solutions. Mixing them up does not just make your code messy — it makes your application insecure. This series separates them cleanly, builds each from first principles with production FastAPI code, and ends with a decision framework for choosing the right approach for your system.

Authentication vs. Authorization

The distinction is simple enough to fit on a napkin:

  • Authentication (AuthN): “Who are you?” — Verifying identity.
  • Authorization (AuthZ): “What can you do?” — Verifying permissions.

A 401 Unauthorized response means “I don’t know who you are” — despite the misleading HTTP status name, it is an authentication failure. A 403 Forbidden response means “I know who you are, and the answer is no.” If your application returns 403 to an unauthenticated request, you have leaked information — the attacker now knows the resource exists.

The Nightclub Analogy

Think of a building with two checkpoints:

  1. The bouncer at the door (authentication) checks your ID. Are you who you claim to be? Valid ID → you enter. Fake ID → you are turned away. The bouncer does not care whether you are on the VIP list.

  2. The host inside (authorization) checks the guest list. You have a valid ID, but are you allowed in the VIP section? On the list → access granted. Not on the list → you can stay in the general area but cannot pass the velvet rope.

Most security bugs happen when applications skip the bouncer and go straight to the guest list, or worse, when the bouncer and the host are the same person using the same checklist.

The Four-Layer Auth Stack

Every auth system — from a weekend project to a bank — has four layers. Some are explicit; some are implicit. Leaving any layer implicit is where vulnerabilities hide.

LayerQuestionFailure ModeExample Fix
TransportIs the channel secure?Eavesdropping, MITMTLS everywhere, HSTS headers
IdentityWho is making this request?Impersonation, credential theftbcrypt hashing, JWT validation
Access ControlAre they allowed this action?Privilege escalation, data leakRBAC, ABAC, scope checks
AuditWhat happened, and can we prove it?Undetected breaches, compliance failureAppend-only logs, event sourcing

When you read about a data breach, trace the root cause back to this stack. Almost every breach is a failure at one of these four layers — usually identity (stolen credentials) or access control (missing permission check).

The Threat Landscape for APIs

Web applications and APIs face overlapping but distinct threats. Since we are building FastAPI backends, here are the threats that should shape our auth decisions:

Credential-Based Attacks

  • Brute force: Automated password guessing. Mitigated by rate limiting and account lockout.
  • Credential stuffing: Using breached username/password pairs from other sites. Mitigated by multi-factor authentication and breach detection.
  • Phishing: Tricking users into entering credentials on a fake site. Mitigated by WebAuthn/FIDO2 and user education.

Token-Based Attacks

  • Token theft: Stealing JWTs or session tokens from storage, logs, or network traffic. Mitigated by short expiry, secure storage, and TLS.
  • Token replay: Reusing a valid token after the user has logged out. Mitigated by token revocation lists or short-lived tokens.
  • Token forgery: Creating fake tokens. Mitigated by proper signing (HMAC or RSA) and key rotation.

API-Specific Attacks

  • Broken Object Level Authorization (BOLA): Accessing another user’s resources by changing an ID in the URL. The #1 API vulnerability in the OWASP API Security Top 10.
  • Broken Function Level Authorization: Calling admin endpoints without admin privileges. A missing Depends() in FastAPI.
  • Mass Assignment: Sending extra fields in a request body to modify protected attributes. Mitigated by strict Pydantic schemas.

FastAPI’s Security Toolkit

FastAPI provides a set of dependency-injection utilities in fastapi.security that handle the transport of credentials — extracting tokens from headers, cookies, or query parameters. They do not validate credentials or check permissions. That is your job.

from fastapi.security import (
OAuth2PasswordBearer, # Extracts Bearer token from Authorization header
OAuth2PasswordRequestForm, # Parses username/password from form data
HTTPBasic, # Extracts Basic auth credentials
HTTPBearer, # Extracts Bearer token (simpler than OAuth2 scheme)
APIKeyHeader, # Extracts API key from a custom header
APIKeyQuery, # Extracts API key from query parameter
APIKeyCookie, # Extracts API key from cookie
)

Each of these is a dependency — a callable that FastAPI injects into your route handler via Depends(). They return the raw credential (a string) or raise HTTPException(401) if the credential is missing.

Here is the simplest possible authenticated endpoint:

from fastapi import Depends, FastAPI
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
app = FastAPI()
security = HTTPBearer()
@app.get("/protected")
async def protected_route(
credentials: HTTPAuthorizationCredentials = Depends(security),
):
# credentials.scheme == "Bearer"
# credentials.credentials == the actual token string
# YOU must validate this token — FastAPI only extracted it
return {"token_received": credentials.credentials[:10] + "..."}

FastAPI extracts. You validate. This separation is intentional and important. The framework does not know whether your token is a JWT, a session ID, or an API key. It only knows where to find it in the request.

The Dependency Chain Pattern

The real power of FastAPI’s auth model is composing dependencies. You build small, focused dependencies and chain them:

from fastapi import Depends, HTTPException, status
# Layer 1: Extract token
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
# Layer 2: Validate token → return user
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = await validate_token(token) # Your logic
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
return user
# Layer 3: Check permissions
async def require_admin(user: User = Depends(get_current_user)):
if not user.is_admin:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
return user
# Route: fully protected
@app.delete("/users/{user_id}")
async def delete_user(user_id: int, admin: User = Depends(require_admin)):
...

Each dependency has one job. Each can be tested independently. And if a dependency raises an exception, the entire chain stops — the route handler never executes.

Authentication Methods Overview

Before we dive into each method in subsequent posts, here is the landscape. Every authentication method answers the same question — “Who are you?” — but each makes different tradeoffs:

MethodCredentialStateful?Best ForWeakness
Password + SessionUsername/password → session cookieYes (server stores sessions)Traditional web apps, server-rendered UIsSession storage scaling, CSRF
JWT (Bearer Token)Username/password → signed tokenNo (token is self-contained)SPAs, mobile apps, microservicesToken revocation, size
API KeyPre-shared key in headerDepends on implementationMachine-to-machine, third-party integrationsKey rotation, no user context
OAuth 2.0Delegated authorization via tokensDepends on grant type”Login with Google,” third-party API accessComplexity, many moving parts
OpenID ConnectOAuth 2.0 + ID tokenNo (ID token is JWT)Single Sign-On, enterprise SSORequires OIDC provider
mTLSClient certificateNo (certificate is self-contained)Service mesh, zero-trust internal APIsCertificate management

We will build each of these in FastAPI across the next seven posts. By the end, you will understand not just how each works, but when to choose one over another — and how to combine them in a production system.

What We Are Building

Throughout this series, we will use a running example: CloudDocs, a document-sharing API. It is small enough to understand quickly but has natural auth requirements:

  • Users sign up, log in, and manage their profiles (password auth, JWT, SSO)
  • Documents are created, read, updated, and deleted (BOLA prevention, ownership checks)
  • Organizations group users with different roles (RBAC, authorization)
  • API clients integrate via programmatic access (API keys, OAuth scopes)
  • Audit logs track every state change (compliance, debugging)

Each post adds one auth layer to CloudDocs with complete, runnable FastAPI code.

What’s Next

In Part 2, we start with the most familiar auth method: passwords. You will learn why bcrypt exists, how to build registration and login endpoints, and why session cookies are still the right choice for server-rendered applications.

But first, make sure your mental model is solid:

  • Authentication verifies identity (who). Authorization verifies permissions (what).
  • 401 means “unknown.” 403 means “known but denied.”
  • FastAPI’s security utilities extract credentials. You validate them.
  • Every auth system has four layers: transport, identity, access control, audit.

Get these right, and every auth implementation in this series will feel like a logical extension rather than a pile of magic code.

0

Next in this series

FastAPI Auth: Password Authentication Done Right

Continue reading