Step-by-step: add production authentication to your FastAPI project.
Every production API needs authentication. This guide walks through implementing both JWT bearer tokens (for user-facing auth) and API keys (for B2B service-to-service auth) in FastAPI, with per-key rate limiting and role-based access control. FastAPI AI Kit includes all of this pre-configured — this guide explains how it works under the hood.
Set up the auth dependencies
Install PyJWT and passlib for token generation and password hashing. Create a security module that FastAPI's dependency injection system will use across all protected endpoints.
# requirements.txt additions
PyJWT>=2.8.0
passlib[bcrypt]>=1.7.4
# app/core/security.py
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, APIKeyHeader
bearer_scheme = HTTPBearer()
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)Create JWT token generation and verification
Build functions to create and verify JWT tokens with configurable expiry, issuer claims, and role-based payloads. Use RS256 or HS256 depending on whether you need token verification by third-party services.
import jwt
from datetime import datetime, timedelta
SECRET_KEY = settings.JWT_SECRET
ALGORITHM = "HS256"
def create_access_token(user_id: str, roles: list[str], expires_minutes: int = 30) -> str:
payload = {
"sub": user_id,
"roles": roles,
"exp": datetime.utcnow() + timedelta(minutes=expires_minutes),
"iat": datetime.utcnow(),
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def verify_token(token: str) -> dict:
try:
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")Implement API key authentication
Create an API key system where keys are stored as hashed values in PostgreSQL. Each key has an associated tier, rate limit, and owner — enabling per-customer access control.
from sqlalchemy import select
from app.models import APIKey
async def verify_api_key(
api_key: str = Depends(api_key_header),
db: AsyncSession = Depends(get_db),
) -> APIKey:
if not api_key:
raise HTTPException(status_code=401, detail="API key required")
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
result = await db.execute(select(APIKey).where(APIKey.key_hash == key_hash, APIKey.is_active == True))
key_record = result.scalar_one_or_none()
if not key_record:
raise HTTPException(status_code=401, detail="Invalid API key")
return key_recordAdd rate limiting per API key
Use Redis to track request counts per API key with sliding window rate limiting. Each key's tier determines its allowed requests per minute.
import redis.asyncio as redis
rate_limiter = redis.from_url(settings.REDIS_URL)
async def check_rate_limit(key: APIKey) -> None:
window_key = f"rate:{key.id}:{int(time.time()) // 60}"
count = await rate_limiter.incr(window_key)
if count == 1:
await rate_limiter.expire(window_key, 60)
if count > key.rate_limit_per_minute:
raise HTTPException(status_code=429, detail="Rate limit exceeded")Wire auth into your endpoints
Use FastAPI's Depends() system to protect endpoints with either JWT or API key auth. The dependency injection makes auth a one-line addition to any endpoint.
@router.post("/v1/chat")
async def chat(
body: ChatRequest,
key: APIKey = Depends(verify_api_key), # one-line auth
):
await check_rate_limit(key)
response = await llm.chat(messages=body.messages)
await meter.record(key.id, response.tokens)
return ChatResponse(reply=response.content)Skip the setup, start building
Everything in this guide is already implemented and configured in FastAPI AI Kit. Clone the repo and start building your product features immediately.
Ready to ship your AI backend this weekend?
Join developers who skipped weeks of boilerplate and went straight to building.