Join our Newsletter — 33% off our NHI Course
Home› Guides› FastAPI Security Guide: Dependencies, OAuth2 Scopes, JWT and…
Guide Application Security

FastAPI Security Guide: Dependencies, OAuth2 Scopes, JWT and Pydantic Validation

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 27 October 2026 12 min read
On this page

FastAPI makes it easy to ship a Python API in an afternoon, and just as easy to ship one where every endpoint is open, because authentication and authorisation only exist where a developer declares a dependency for them. FastAPI is now the default framework for many Python back ends, AI inference services, agent tool servers and MCP servers, so its security model decides who can call model endpoints, read data and spend API credits. This guide explains how identity and access actually work in FastAPI (security schemes as dependencies, OAuth2 scopes through Security(), JWT handling), how to structure admin and privileged routes, how to handle API keys and service accounts, what to log, a hardening baseline for production, and the misconfigurations seen most often. It is written for Python developers, API security engineers and reviewers. It assumes the general material in the API Security Guide and the JWT Security Guide, and focuses on what is specific to FastAPI.

Key takeaways

  • Security is opt-in per route. A path operation is protected only if it, its router or the app declares a security dependency; a missing Depends is an open endpoint, not an error.
  • Put protection at the router or app level. FastAPI(dependencies=[...]) and APIRouter(dependencies=[...]) apply a dependency to every route beneath them, which makes "secure by default, open by exception" possible.
  • Scopes are documented, not enforced, by OpenAPI. Security(..., scopes=[...]) and SecurityScopes tell your code which scopes are required, but your dependency must check the token and reject it.
  • Validate tokens fully. Pin the algorithm list in jwt.decode, check expiry, issuer and audience, and never place scopes in a token simply because the client asked for them.
  • Pydantic ignores unknown fields by default. Use extra="forbid" on input models and separate response models so that clients cannot set, or see, fields they should not.

How identity and access work in FastAPI

FastAPI has no built-in user store, session system or permission model. Instead it offers security scheme classes in fastapi.security that extract credentials from a request and register the scheme in the OpenAPI document, and a dependency injection system that runs your verification code before the route handler.

Building blockWhat it doesWhat it does not do
OAuth2PasswordBearer(tokenUrl="token")Reads a bearer token from the Authorization header, returns 401 if missing, documents the flowValidate the token, check signature, expiry or scopes
OAuth2PasswordRequestFormParses username, password and requested scopes from a form postAuthenticate the user or limit which scopes they get
HTTPBearer, HTTPBasicExtract bearer or basic credentialsCompare them safely; for basic auth use secrets.compare_digest() on UTF-8 bytes
APIKeyHeader, APIKeyQuery, APIKeyCookieExtract an API key from a header, query string or cookieLook up, hash, scope or expire the key
Depends()Runs a dependency and injects its result; dependencies can depend on othersCarry scope requirements
Security()A subclass of Depends with a scopes parameter; scopes accumulate down the dependency tree into SecurityScopesReject a token lacking scopes, unless your code does

The verification dependency

The official tutorial pattern is a get_current_user dependency that decodes the JWT with PyJWT, reads the sub claim, loads the user and raises HTTPException with status 401, detail="Could not validate credentials" and headers={"WWW-Authenticate": "Bearer"} on any InvalidTokenError. Production versions should add:

  • Algorithm pinning: pass an explicit list, for example algorithms=["RS256"], and never derive it from the token header.
  • Issuer and audience: require iss and aud and pass expected values, so tokens for another API or tenant are rejected.
  • Expiry: require exp; the tutorial uses 30 minutes for access tokens and a 15-minute default.
  • Unique subjects: the tutorial advises that sub be a string unique across the application, and suggests a prefix such as username:johndoe to avoid collisions between entity types. Use the same idea for service principals, for example svc: or agent:.
  • Key source: for tokens from an external identity provider, verify against the provider's JWKS with RS256 or ES256 (pyjwt[crypto]) rather than a shared HS256 secret.

Scopes with Security() and SecurityScopes

Declare scopes on OAuth2PasswordBearer(scopes={...}) for documentation, then require them per route with Security(get_current_active_user, scopes=["items"]). Scopes required by every dependant accumulate: in the tutorial, /users/me/items/ needs both items and me. Inside the dependency, security_scopes.scopes lists what is required and security_scopes.scope_str fills the WWW-Authenticate: Bearer scope="..." header. The tutorial's check loops over required scopes and raises 401 "Not enough permissions" for any missing; many teams return 403 for an authenticated caller who lacks a scope, which better matches HTTP semantics. The documentation warns that its example adds the requested scopes straight into the token for simplicity, and that production code must only issue scopes the user is entitled to.

Passwords, if you must store them

The current tutorial uses pwdlib with Argon2 (PasswordHash.recommended()) and verifies a dummy hash when the username does not exist, so response times do not reveal valid usernames. Most production systems should delegate user authentication to an identity provider and keep FastAPI as a resource server. See the OAuth 2.0 and OpenID Connect Guide.

Admin roles and privileged access

Admin endpoints are where a single missing dependency does most damage, mapping directly to OWASP API5:2023 Broken Function Level Authorization.

  • Separate router: mount admin routes on an APIRouter(prefix="/admin", dependencies=[Security(require_admin, scopes=["admin"])]) so that new admin routes inherit protection automatically.
  • Role from the identity provider: map roles or groups from validated token claims, not from request bodies or headers the client controls.
  • Object-level checks in the handler or service layer: scopes say what kind of action is allowed; they do not say which record. Check ownership or tenancy for every object ID (API1:2023 Broken Object Level Authorization). See the OWASP API Top 10 API1 Broken Object Level Authorization Guide.
  • Property-level control: use different Pydantic models for create, update and admin update, so fields such as is_admin or tenant_id cannot be mass-assigned (API3:2023).
  • Step-up for destructive actions: for deletion, key rotation or bulk export, require a fresh token or a specific high-privilege scope with a short lifetime.
  • Docs are an admin surface: Swagger UI at /docs, ReDoc at /redoc and the schema at /openapi.json reveal every route. Disable them in production with openapi_url=None (which also disables both UIs), or serve them only behind authentication.

Integrations, API tokens and service accounts

Many FastAPI services are called mostly by machines: other microservices, scheduled jobs, AI agents, and MCP clients. These callers are non-human identities and need the same lifecycle as user accounts. See the API Keys vs OAuth Tokens Guide.

CallerRecommended credentialFastAPI implementation notes
Internal microserviceOAuth client credentials token or mTLS identityValidate the token in a router-level dependency; map sub or client ID to a service principal with explicit scopes. See the Machine-to-Machine OAuth (Client Credentials) Guide.
Partner or customer integrationScoped API key or OAuth clientAPIKeyHeader(name="X-API-Key"), look up a hashed key, compare with secrets.compare_digest(), record owner, scopes and expiry
AI agent or MCP clientShort-lived delegated OAuth token with narrow scopesTreat each tool as a scoped route; log the agent identity and the user it acts for. See the Remote MCP Server Hardening Guide.
Outbound calls from FastAPI to LLM providers and SaaSSecrets from a vault or environment injectionLoad with Pydantic settings at startup, never return them in responses or errors, rotate on staff or supplier change

Two practices prevent most incidents. First, never accept API keys in query strings (APIKeyQuery) for anything sensitive, because URLs end up in proxy and access logs. Second, keep the signing secret out of source: the tutorial's openssl rand -hex 32 instruction is for generating a value, and the documentation tells readers not to reuse its example key. Exposed back ends with no authentication are a recurring cause of AI-era breaches: the ShadowRay campaign abused an unauthenticated Python AI cluster API to steal cloud and AI tokens, and the McKinsey Lilli incident began with documented API endpoints that required no authentication.

Logging and detection

FastAPI does not log authentication decisions by itself. Add a middleware or a dependency that writes structured events, and keep tokens and secrets out of them.

EventWhere to capture itWhy it matters
Authentication failure (401) with reason categorySecurity dependencyCredential stuffing and token replay (ATT&CK T1110 Brute Force, T1550.001 Application Access Token)
Authorisation failure (403 or scope denial)Security dependency or service layerObject ID enumeration and privilege probing
Successful privileged actionAdmin router dependencyAudit trail for admin and destructive actions
Principal type and ID (user, service, agent) on every requestMiddleware after authenticationAttribute machine and agent activity
Validation errors (422) by routeException handler for RequestValidationErrorFuzzing and injection attempts
Rate limit triggers and 503 from concurrency limitsProxy, gateway or Uvicorn --limit-concurrencyResource abuse (API4:2023), denial of service (T1499)

Metrics

  • Unprotected route count: routes with no security dependency in the resolved dependency tree, excluding an approved public list (target zero unexplained).
  • Token validation failure rate: 401s per thousand requests by route and client.
  • Machine credential hygiene: share of API keys and service principals with owner, scopes and expiry recorded.
  • Scope breadth: share of tokens issued with admin or wildcard scopes.
  • Time to revoke: minutes from a compromised key report to revocation in production.

Hardening baseline

  1. Default deny: declare the authentication dependency on the FastAPI app or every APIRouter, and put genuinely public routes (health, login) on a separate router.
  2. Strict input models: set model_config = ConfigDict(extra="forbid") on input models (Pydantic otherwise ignores extra fields), use Field constraints for lengths and ranges, and set a response_model on every route so internal fields are filtered out. See the Input Validation and Output Encoding Guide.
  3. Disable or protect docs: FastAPI(openapi_url=None) in production, or protect the docs routes with a dependency.
  4. CORS by allowlist: CORSMiddleware defaults are restrictive (allow_methods=["GET"], allow_credentials=False, max_age=600). With allow_credentials=True none of origins, methods or headers may be ["*"], so list origins explicitly. See the CORS Misconfiguration Guide.
  5. Host and scheme: add TrustedHostMiddleware(allowed_hosts=[...]) to reject unexpected Host headers with 400, and terminate TLS at a proxy or use HTTPSRedirectMiddleware.
  6. Proxy headers only from the proxy: the server ignores X-Forwarded-* headers unless told to trust them. Use --forwarded-allow-ips="*" only when the app is reachable solely through a trusted proxy; otherwise clients can spoof their IP and scheme.
  7. Resource limits: set request size limits and rate limits at the proxy or gateway, and use Uvicorn options such as --limit-concurrency (returns 503 past the limit) and --timeout-keep-alive (default 5 seconds). See the Rate Limiting and API Abuse Guide.
  8. Error handling: return generic messages, keep stack traces out of responses, and never run with debug settings in production.
  9. Dependencies and supply chain: pin and scan FastAPI, Starlette, Pydantic, PyJWT and their transitive dependencies; Python package compromises such as the Ultralytics PyPI compromise show why. See the Python Security Guide.

Roll out in stages: add router-level dependencies in report-only mode (log, do not block) to find callers that would break, then enforce per router, keeping a feature flag to revert one router at a time.

Mapping to frameworks

FastAPI controlOWASP API Top 10 2023OWASP ASVS 5.0 areaNIST SP 800-53 Rev 5
Router-level security dependenciesAPI2, API5V6 Authentication, V8 AuthorizationAC-3, IA-2
Object ownership checksAPI1V8 AuthorizationAC-3
Separate input and response models, extra="forbid"API3V2 Validation and Business LogicSI-10
Rate and concurrency limitsAPI4, API6V4 API and Web ServiceSC-5
Docs disabled, CORS and host allowlistsAPI8V13 ConfigurationCM-7
Route inventory from the OpenAPI schemaAPI9V13 ConfigurationCM-8
Key and token lifecycleAPI2V9 Self-contained TokensIA-5

Common misconfigurations

  • One route without a dependency: security is declared per path operation, a new route is added without it, and it goes live open. Router-level dependencies prevent this.
  • Trusting requested scopes: copying form_data.scopes straight into the token, as the tutorial does for simplicity and explicitly warns against.
  • Weak JWT handling: HS256 with a secret committed to the repository, no audience check, or long-lived tokens with no revocation path.
  • Mass assignment through shared models: one Pydantic model for create, update and read lets clients set privileged fields or see internal ones.
  • Wildcard CORS and trusted proxy headers everywhere: allow_origins=["*"] combined with cookie auth workarounds, or --forwarded-allow-ips="*" on a directly reachable server.
  • Public docs and schema: /docs and /openapi.json left open in production, mapping every internal and admin route for an attacker.

Practitioner checklist

  • Declare authentication on the app or every router and keep an explicit public route allowlist.
  • Pin JWT algorithms and validate exp, iss and aud in one shared dependency.
  • Issue only entitled scopes and check them with Security() and SecurityScopes.
  • Enforce object-level and tenant checks for every ID in a path or body.
  • Use separate Pydantic input and response models with extra="forbid" on inputs.
  • Register every API key and service or agent principal with owner, scopes and expiry; compare keys in constant time.
  • Disable or protect /docs, /redoc and /openapi.json in production.
  • Allowlist CORS origins and hosts; trust forwarded headers only from the proxy.
  • Log authentication, authorisation and privileged events with principal type, without tokens.
  • Pin and scan FastAPI, Starlette, Pydantic and PyJWT dependencies.

Standards and references

Related NHI Mgmt Group resources: API Security Guide · JWT Security Guide · Python Security Guide · Remote MCP Server Hardening Guide · Django Security Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 27 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org