DEV Community

Jane for Mastering Backend

Posted on • Originally published at masteringbackend.com on

FastAPI Authentication Fundamentals

title

Building secure APIs is essential. Whether you’re protecting user data, securing business logic, or managing access to premium features, authentication forms the backbone of your application’s security.

The challenge? Authentication often resembles a complex maze of tokens, sessions, headers, and security protocols. When you add the pressure of selecting the most appropriate approach for your specific use case, it’s easy to feel overwhelmed.

Here’s the good news : FastAPI simplifies authentication, making it both powerful and user-friendly. With its built-in security features, automatic documentation generation, and Python’s straightforward syntax, you can implement strong authentication without the hassle.

In this guide, we’ll walk through three core ways to authenticate users:

  • Basic HTTP authentication : Ideal for internal APIs, microservices, and rapid prototyping
  • API key authentication : Preferred for public APIs, third-party integrations, and automated systems.
  • Session-based authentication : Ideal for traditional web applications and user-facing interfaces.

By the end, you’ll understand not just how to implement each method, but when to use them, along with the security best practices.

Authentication vs Authorization

These two often get mixed up. Here’s the quick breakdown:

  • Authentication is like checking someone’s ID at the door. It gives an answer to the question: “Who are you?”. When a user logs in with their username and password, authentication happens.
  • Authorization is like checking if that person has VIP access. It gives an answer to the question: “What are you allowed to do?”. Once you know who someone is, you decide if they can access admin features, delete posts or even give access to other people.

Think of it this way: authentication gets you into the building, authorization determines which rooms you can enter. FastAPI helps with both, but we’re focusing on authentication in this article.

Setting Up

Let’s start with the basic project structure and dependencies:

Create a new project directory:

mkdir fastapi-auth-demo && cd fastapi-auth-demo
Enter fullscreen mode Exit fullscreen mode

Set up the virtual environment:

# Create virtual environment 
python -m venv venv 

# Activate it 
./venv/Scripts/activate # Windows 
source venv/bin/activate # macOS/Linux
Enter fullscreen mode Exit fullscreen mode

Create a requirements.txt and add these dependencies:


fastapi==0.104.1 
uvicorn[standard]==0.24.0 
python-multipart==0.0.6 
passlib[bcrypt]==1.7.4 
python-jose[cryptography]==3.3.0 
itsdangerous==2.2.0
Enter fullscreen mode Exit fullscreen mode

Install the dependencies:

pip install -r ./requirements.txt
Enter fullscreen mode Exit fullscreen mode

Now let’s create our main FastAPI app with some basic setup:

from fastapi import FastAPI 
from fastapi.middleware.cors import CORSMiddleware 

app = FastAPI(title="FastAPI Authentication Demo", version="1.0.0") 

# Add CORS middleware 
app.add_middleware( 
    CORSMiddleware, 
    allow_origins=["*"], 
    allow_credentials=True, 
    allow_methods=["*"], 
    allow_headers=["*"], )
Enter fullscreen mode Exit fullscreen mode

Basic HTTP Authentication

Basic HTTP Authentication is the simplest form of authentication, where credentials (username and password) are sent with each request. This type of authentication is most suitable for internal API or quick prototypes.

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

security = HTTPBasic() 

fake_users_db = { 
    "john": {"username": "john", "password": "secret123", "role": "user"}, 
    "admin": {"username": "admin", "password": "supersecret", "role": "admin"} 
} 

def authenticate_user(credentials: HTTPBasicCredentials = Depends(security)): 
    user = fake_users_db.get(credentials.username) 
    if not user or not secrets.compare_digest(credentials.password, user["password"]): 
        raise HTTPException(status_code=401, detail="Invalid credentials") 
    return user 

@app.get("/basic/profile") 
def read_profile(user: dict = Depends(authenticate_user)): 
    return {"message": f"Welcome {user['username']}!"} 

@app.get("/basic/admin") 
def admin_area(user: dict = Depends(authenticate_user)): 
    if user["role"] != "admin": 
       raise HTTPException(status_code=403, detail="Admins only") 
    return {"message": "You're in the admin zone."}
Enter fullscreen mode Exit fullscreen mode

API Key Authentication

API key authentication is popular for API access, where clients include a key in headers or query parameters. Instead of sending a username and password for every request, clients send a special key that identifies them.

from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials 

api_key_header = HTTPBearer() 

fake_api_keys = { 
    "sk_test_123": {"key_id": "1", "permissions": ["read", "write"]}, 
    "sk_live_456": {"key_id": "2", "permissions": ["read"]} 
 } 

 def validate_api_key(credentials: HTTPAuthorizationCredentials = Depends(api_key_header)): 
     key = credentials.credentials 
     if key not in fake_api_keys: 
          raise HTTPException(status_code=401, detail="Invalid API Key") 
      return fake_api_keys[key] 

 @app.get("/apikey/data") 
 def read_data(api_key=Depends(validate_api_key)): 
     return {"data": ["item1", "item2"]} 

@app.post("/apikey/create") 
def create_item(item: dict, api_key=Depends(validate_api_key)): 
    if "write" not in api_key["permissions"]: 
        raise HTTPException(status_code=403, detail="Write access required") 
    return {"message": "Item created", "item": item}
Enter fullscreen mode Exit fullscreen mode

Session-Based Authentication

Session-based authentication stores user state on the server and uses session cookies for subsequent requests. When you log in, the server creates a session and gives you a cookie. It’s like getting a wristband at a concert — as long as you have it, you can move around freely.


from fastapi import FastAPI, Request, Form, Depends, HTTPException, status 
from fastapi.responses import JSONResponse 
from starlette.middleware.sessions import SessionMiddleware 
from pydantic import BaseModel 

# Add session middleware 
app.add_middleware(SessionMiddleware, secret_key="super-secret-session-key") 

# Mock user database 
users_db = { 
    "alice": "password123", 
    "bob": "securepass" 
} 

class LoginSchema(BaseModel): 
    username: str 
    password: str 

@app.post("/login") 
async def login(request: Request, payload: LoginSchema): 
    username = payload.username 
    password = payload.password 

    if username in users_db and users_db[username] == password: 
        request.session["user"] = username 
        return JSONResponse({"message": "Login successful"}) 
        raise HTTPException(status_code=401, detail="Invalid credentials") 

@app.get("/dashboard") 
async def dashboard(request: Request): 
    user = request.session.get("user") 
    if not user: 
        raise HTTPException(status_code=401, detail="Not authenticated") 
    return {"message": f"Welcome to your dashboard, {user}!"} 

  @app.post("/logout") 
  async def logout(request: Request): 
      request.session.clear() 
      return {"message": "Logged out successfully"}
Enter fullscreen mode Exit fullscreen mode

Error Handling

This article dives deeper in various HTTP responses and their error handling techniques. Consistent error handling improves security and user experience:

from fastapi.exceptions import RequestValidationError 
from fastapi.responses import JSONResponse 

@app.exception_handler(HTTPException) 
async def http_exception_handler(request, exc): 
    """Custom HTTP exception handler""" 
    return JSONResponse( 
        status_code=exc.status_code, 
        content={ 
            "error": { 
                "message": exc.detail, 
                "type": "authentication_error" if exc.status_code == 401 else "authorization_error", 
                "status_code": exc.status_code 
             } 
          }, 
          headers=exc.headers 
      ) 

 @app.exception_handler(RequestValidationError) 
async def validation_exception_handler(request, exc): 
    """Handle validation errors""" 
    return JSONResponse( 
        status_code=422, 
        content={ 
            "error": { 
                "message": "Validation error", 
                "type": "validation_error", 
                "details": exc.errors() 
           } 
        } 
     )
Enter fullscreen mode Exit fullscreen mode

Security Best Practices

1. Never store plain text passwords, always hash password using bcrypt:


from passlib.context import CryptContext 

# Use proper password hashing 
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 

def hash_password(password: str) -> str: 
    return pwd_context.hash(password)
Enter fullscreen mode Exit fullscreen mode

2. Rate Limiting

from collections import defaultdict 
import time 

# Simple rate limiting (use Redis in production) 
request_counts = defaultdict(list) 

def rate_limit(max_requests: int = 100, window_minutes: int = 15): 
    def decorator(func): 
        def wrapper(*args, **kwargs): 
            client_ip = "127.0.0.1" 
            now = time.time() 
            window_start = now - (window_minutes * 60) 

            # Clean old requests 
            request_counts[client_ip] = [ 
                req_time for req_time in request_counts[client_ip] 
                if req_time > window_start 
            ] 

            if len(request_counts[client_ip]) >= max_requests: 
                raise HTTPException( 
                    status_code=429, 
                    detail="Rate limit exceeded" 
                 ) 

             request_counts[client_ip].append(now) 
             return func(*args, **kwargs) 
          return wrapper 
       return decorator
Enter fullscreen mode Exit fullscreen mode

3. Use environment variables for secrets, with pydantic settings. Never hard code secret keys in your code:


# config.py 
from pydantic_settings import BaseSettings 

class Settings(BaseSettings): 
    secret_key: str = "your-secret-key-here" 
    algorithm: str = "HS256" 
    access_token_expire_minutes: int = 30 

    class Config: 
        env_file = ".env" 

settings = Settings()
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

We’ve covered three authentication strategies in FastAPI:

  • Basic HTTP Authentication : Simple but suitable for internal APIs.
  • API Key Authentication : Great for public APIs and service-to-service communication.
  • Session-Based Authentication : Traditional and cookie based approach.

Each method has its use cases, and the choice depends on your application’s requirements.

Key Takeaways:

  • Always use HTTPS in production.
  • Hash passwords properly with bcrypt or similar.
  • Implement proper error handling and logging.
  • Add security headers and middleware.
  • Test your authentication thoroughly.
  • Add rate limiting for public APIs.
  • Use environment variables to store sensitive data

Dig Deeper

Have a great one!!!

Author: Jane Nkwor


Thank you for being a part of the community

Before you go:

Whenever you’re ready

There are 4 ways we can help you become a great backend engineer:

  • The MB Platform: Join thousands of backend engineers learning backend engineering. Build real-world backend projects, learn from expert-vetted courses and roadmaps, track your learnings and set schedules, and solve backend engineering tasks, exercises, and challenges.
  • The MB Academy: The “MB Academy” is a 6-month intensive Advanced Backend Engineering Boot Camp to produce great backend engineers.
  • Join Backend Weekly: If you like posts like this, you will absolutely enjoy our exclusive weekly newsletter, sharing exclusive backend engineering resources to help you become a great Backend Engineer.
  • Get Backend Jobs: Find over 2,000+ Tailored International Remote Backend Jobs or Reach 50,000+ backend engineers on the #1 Backend Engineering Job Board.

Originally published at https://masteringbackend.com on September 2, 2025.


Top comments (0)