Dev.to Security 🔐 Cybersecurity 👁 0 📖 2 min read

Building a Secure API Starter Without Hiding the Hard Parts

Building a Secure API Starter Without Hiding the Hard Parts Exposing REST APIs to the public internet requires defense-in-depth security. Too many tutorial implementations cut corners by skipping rate limiting, using s

Building a Secure API Starter Without Hiding the Hard Parts

Exposing REST APIs to the public internet requires defense-in-depth security. Too many tutorial implementations cut corners by skipping rate limiting, using simple database lookups for every request, or storing JSON Web Tokens (JWTs) in browser LocalStorage.

Here is how to design a secure FastAPI starter that addresses authentication, rate limiting, and secure storage from day one.

1. Defense-in-Depth Architecture

To build a secure API, you need multiple layers of protection:

[ Client ] 
   │
   ▼
[ Rate Limiting Layer (Token Bucket / Redis) ]
   │
   ▼
[ CORS Policy Guards ]
   │
   ▼
[ Stateless JWT / OAuth2 Security Middleware ]
   │
   ▼
[ Controller / Role-Based Access Control (RBAC) ]

2. Stateless Auth & JWT Revocation

While stateless JWT authentication is clean, token revocation (for logouts or password resets) is often neglected. We solve this by adding a Redis-backed token blacklist. We store the token's unique identifier (jti) in Redis with a Time-To-Live (TTL) matching the token's remaining lifespan.

Here is the FastAPI implementation:

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

SECRET_KEY = "your-high-entropy-source-key"
ALGORITHM = "HS256"
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# Mock Redis blacklist for demonstration
token_blacklist = set()

def revoke_token(jti: str):
    token_blacklist.add(jti)

def is_token_revoked(jti: str) -> bool:
    return jti in token_blacklist

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")
        jti: str = payload.get("jti")
        if username is None or jti is None:
            raise credentials_exception
        if is_token_revoked(jti):
            raise HTTPException(status_code=401, detail="Token has been revoked")
    except JWTError:
        raise credentials_exception

    return {"username": username, "role": payload.get("role", "user")}

3. Threat Modeling & Secure Storage

Token Storage Recommendations

  1. Never store tokens in localStorage: They are vulnerable to Cross-Site Scripting (XSS) attacks.
  2. Use HttpOnly, Secure, SameSite=Strict Cookies: This ensures the browser only sends tokens during first-party requests and hides them from client-side scripts.
  3. Mobile Client Storage: Exclusively utilize hardware-backed security modules (Keychain for iOS, Keystore for Android).
📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.