Skip to main content

FastAPI Security Dependencies

Introduction

Security is a critical aspect of any web application. In FastAPI, one of the most elegant ways to implement security measures is through dependency injection - a powerful feature that allows you to create reusable security components.

Security dependencies in FastAPI enable you to:

  • Control access to your API endpoints
  • Validate user credentials
  • Apply consistent security policies across your application
  • Extract and validate authentication tokens
  • Implement role-based access control

This guide will walk you through various ways to use dependencies for securing your FastAPI applications, from basic authentication to more complex security schemes.

Understanding Dependencies in FastAPI

Before diving into security dependencies, let's briefly understand what dependencies are in FastAPI.

Dependencies in FastAPI are functions that can be "injected" into path operation functions. When a request is made to an endpoint, FastAPI will:

  1. Execute all dependencies first
  2. Use their return values as parameters for your endpoint function
  3. Return an error if any dependency fails
python
from fastapi import Depends, FastAPI

app = FastAPI()

async def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}

@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons

This simple example demonstrates the basics of dependencies, but now let's see how they can be used for security.

Basic Authentication with Dependencies

One of the simplest forms of authentication is HTTP Basic Authentication, where credentials are sent in the HTTP header.

python
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import secrets

app = FastAPI()

security = HTTPBasic()

def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
correct_username = secrets.compare_digest(credentials.username, "admin")
correct_password = secrets.compare_digest(credentials.password, "password123")

if not (correct_username and correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Basic"},
)

return credentials.username

@app.get("/users/me")
def read_current_user(username: str = Depends(get_current_username)):
return {"username": username}

When you access /users/me, the browser will prompt for a username and password. The dependency get_current_username will verify these credentials before allowing access to the endpoint.

Token-Based Authentication

For more modern applications, token-based authentication (like OAuth2 with Bearer tokens) is preferred. Here's how to implement it:

python
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from datetime import datetime, timedelta
from jose import JWTError, jwt
from pydantic import BaseModel

# Secret key for JWT signing
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

app = FastAPI()

# This creates a reusable dependency that extracts and validates Bearer tokens
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

class Token(BaseModel):
access_token: str
token_type: str

class User(BaseModel):
username: str
email: str = None
full_name: str = None
disabled: bool = None

class UserInDB(User):
hashed_password: str

# Mock user database
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "fakehashedsecret",
"disabled": False,
}
}

def verify_password(plain_password, hashed_password):
# In a real app, you would use proper password hashing
return plain_password + "fakehash" == hashed_password

def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)

def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user

def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt

async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username=username)
if user is None:
raise credentials_exception
return user

async def get_current_active_user(current_user: User = Depends(get_current_user)):
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user

@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}

@app.get("/users/me", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user

This example shows a complete JWT-based authentication system:

  1. Users log in via the /token endpoint and receive a JWT token
  2. The token is then sent in the Authorization header for subsequent requests
  3. The get_current_user dependency extracts and validates this token
  4. The get_current_active_user adds an additional check for account status

Role-Based Access Control

Often you'll want to restrict certain endpoints to users with specific roles. Here's how to implement role-based access using dependencies:

python
from fastapi import Depends, FastAPI, HTTPException, status
from typing import List

app = FastAPI()

# Extend our previous User model
class User(BaseModel):
username: str
email: str = None
full_name: str = None
disabled: bool = None
roles: List[str] = []

# Modify get_current_user to include roles
# ... (previous code remains the same)

def get_admin_user(current_user: User = Depends(get_current_active_user)):
if "admin" not in current_user.roles:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Insufficient permissions"
)
return current_user

@app.get("/users/")
async def read_all_users(current_user: User = Depends(get_admin_user)):
# Only admins can see all users
return [{"username": "user1"}, {"username": "user2"}]

This creates a new dependency get_admin_user that checks if the user has the "admin" role before allowing access.

Scoped Dependencies

OAuth2 allows for scopes that limit what an authenticated user can do. Here's how to implement scope-based security:

python
from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
from pydantic import BaseModel, ValidationError
from typing import List, Optional

app = FastAPI()

# Note: we now use Security instead of Depends
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="token",
scopes={
"users:read": "Read user information",
"users:write": "Create or update users",
"items:read": "Read items",
"items:write": "Create or update items",
},
)

class User(BaseModel):
username: str
email: Optional[str] = None
full_name: Optional[str] = None
disabled: Optional[bool] = None
scopes: List[str] = []

# Modified get_current_user to check scopes
async def get_current_user(
security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
):
if security_scopes.scopes:
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
else:
authenticate_value = "Bearer"

credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": authenticate_value},
)

# Decode token and extract scopes
# ... (omitted for brevity, similar to previous examples)

# Check if user has required scopes
for scope in security_scopes.scopes:
if scope not in user.scopes:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions",
headers={"WWW-Authenticate": authenticate_value},
)

return user

@app.get("/users/me", response_model=User)
async def read_users_me(
current_user: User = Security(get_current_user, scopes=["users:read"])
):
return current_user

@app.post("/users/")
async def create_user(
user_data: dict,
current_user: User = Security(get_current_user, scopes=["users:write"])
):
return {"message": "User created", "created_by": current_user.username}

In this example, the Security dependency wrapper is used instead of Depends to specify which scopes are required for each endpoint.

Dependency Chaining

One of the most powerful features of FastAPI dependencies is the ability to chain them together. This allows you to build complex security systems from simple components:

python
from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()

# Base dependency to get a token
async def get_token(authorization: str = Header(...)):
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Invalid token format")
return authorization.replace("Bearer ", "")

# Dependency that uses get_token
async def decode_token(token: str = Depends(get_token)):
# In real-world, you would decode and validate the token here
return {"user_id": 123, "scopes": ["read", "write"]}

# Dependency that uses decode_token
async def get_current_user(payload: dict = Depends(decode_token)):
# Get user from database based on payload
return {"id": payload["user_id"], "username": "johndoe"}

# Final dependency that uses get_current_user
async def get_active_admin_user(current_user: dict = Depends(get_current_user)):
if current_user["username"] != "admin":
raise HTTPException(status_code=403, detail="Must be admin")
return current_user

@app.get("/admin-panel/")
async def admin_panel(admin: dict = Depends(get_active_admin_user)):
return {"message": "Welcome to the admin panel", "admin": admin}

This approach creates a chain of dependencies that are executed in order, with each one building upon the previous one.

Global Dependencies

Sometimes you want to apply security checks to all or most of your endpoints. FastAPI allows you to add dependencies at the application level:

python
from fastapi import Depends, FastAPI, Header, HTTPException

app = FastAPI()

async def verify_api_key(x_api_key: str = Header(...)):
if x_api_key != "valid_api_key":
raise HTTPException(status_code=400, detail="Invalid API Key")
return x_api_key

# Apply this dependency to all routes
app = FastAPI(dependencies=[Depends(verify_api_key)])

@app.get("/items/")
async def read_items():
# No need to explicitly include the verify_api_key dependency
return [{"id": 1, "name": "Item 1"}]

@app.get("/users/")
async def read_users():
# This also requires a valid API key
return [{"id": 1, "username": "johndoe"}]

Router-Level Dependencies

You can also apply dependencies to a specific group of routes using the APIRouter:

python
from fastapi import APIRouter, Depends, FastAPI

app = FastAPI()

def admin_dependency():
# In a real app, this would verify the user is an admin
return {"is_admin": True}

admin_router = APIRouter(
prefix="/admin",
dependencies=[Depends(admin_dependency)],
responses={418: {"description": "I'm a teapot"}},
)

@admin_router.get("/settings")
async def admin_settings():
# This endpoint is protected by the admin_dependency
return {"admin_settings": "value"}

@admin_router.get("/performance")
async def admin_performance():
# This endpoint is also protected
return {"admin_performance": "data"}

app.include_router(admin_router)

This approach lets you organize your routes by security requirements, making your code more maintainable.

Best Practices for Security Dependencies

When implementing security dependencies in FastAPI, follow these best practices:

  1. Layer your dependencies: Create specific security functions for different needs and combine them.
  2. Use proper error handling: Return appropriate status codes (401 for authentication failures, 403 for permissions issues).
  3. Include WWW-Authenticate headers: These help clients understand how to authenticate.
  4. Cache expensive operations: If a dependency performs database lookups, consider caching results.
  5. Use environment variables for sensitive values like secret keys.
  6. Apply the principle of least privilege: Only request the minimum scopes/permissions needed.
python
# Example of proper error handling in a dependency
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token",
headers={"WWW-Authenticate": "Bearer"},
)

# Continue with user lookup

Real-World Example: API with Multiple Authentication Methods

Many modern APIs support multiple authentication methods. Here's how to implement an API that accepts either an API key or OAuth2 token:

python
from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import APIKeyHeader, OAuth2PasswordBearer
from typing import Optional

app = FastAPI()

# Set up our security schemes
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)

async def get_current_user(
api_key: Optional[str] = Security(api_key_header),
token: Optional[str] = Security(oauth2_scheme),
):
if api_key is None and token is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authentication required",
headers={"WWW-Authenticate": "Bearer or API key"},
)

if api_key:
# Validate API key
if api_key == "valid_api_key":
return {"username": "api_user", "auth_method": "api_key"}
else:
raise HTTPException(status_code=401, detail="Invalid API key")

if token:
# Validate OAuth2 token (simplified for example)
if token == "valid_oauth_token":
return {"username": "oauth_user", "auth_method": "oauth"}
else:
raise HTTPException(
status_code=401,
detail="Invalid token",
headers={"WWW-Authenticate": "Bearer"}
)

@app.get("/secure-data/")
async def get_secure_data(user: dict = Depends(get_current_user)):
return {
"data": "This is secure data",
"authenticated_as": user["username"],
"auth_method": user["auth_method"]
}

This example demonstrates how to create a flexible authentication system that works with different client requirements.

Summary

FastAPI's dependency injection system provides a powerful and flexible way to implement security in your applications. By using dependencies, you can:

  • Create reusable security components
  • Apply different security requirements to different endpoints
  • Chain dependencies to build complex authentication and authorization flows
  • Implement various authentication schemes like Basic Auth, OAuth2, and API keys
  • Control access based on roles and scopes

Security dependencies help you write clean, maintainable code while ensuring your API remains secure and follows best practices.

Additional Resources

Exercises

  1. Create a FastAPI application with an endpoint protected by HTTP Basic Authentication
  2. Implement JWT-based authentication with token expiration
  3. Add role-based access control with at least two different roles
  4. Create an API that requires different scopes for different operations
  5. Implement a system that supports multiple authentication methods (OAuth2 and API key)


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)