Home Python FastAPI Authentication — JWT and OAuth2 with Password Flow

FastAPI Authentication — JWT and OAuth2 with Password Flow

⚡ Quick Answer
FastAPI implements authentication using the OAuth2PasswordBearer class, which acts as a dependency to extract the Bearer token from the 'Authorization' header. Once extracted, you use python-jose to decode the JWT and verify the 'sub' claim. Protecting routes is as simple as adding Depends(get_current_user) to your path operation parameters, ensuring that unauthenticated requests are rejected with a 401 Unauthorized status before the logic executes.

The Authentication Backbone: JWT Configuration

Before handling requests, we must define our security constants. In a real-world scenario at TheCodeForge, we never hardcode secrets. We use environment variables to manage the SECRET_KEY and ALGORITHM. The OAuth2PasswordBearer is initialized here to tell FastAPI where the login 'token' endpoint is located.

io/thecodeforge/auth/config.py · PYTHON
1234567891011121314151617
import os
from datetime import datetime, timedelta, timezone
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext

# Configuration - In production, use os.getenv('SECRET_KEY')
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

app = FastAPI()
▶ Output
Configuration loaded with Bcrypt hashing.

Token Generation and Login Flow

The /token endpoint is the gateway. It receives the OAuth2PasswordRequestForm, verifies the user's identity, and returns a signed JWT. This token contains a 'payload' (usually the username) and an 'expiration' time to prevent replay attacks.

io/thecodeforge/auth/token_logic.py · PYTHON
12345678910111213141516171819202122
def create_access_token(data: dict, expires_delta: timedelta | None = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

@app.post("/token")
async def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
    # Mock user verification - Replace with DB call
    if form_data.username != "forge_dev" or form_data.password != "secret_pass":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    
    access_token = create_access_token(data={"sub": form_data.username})
    return {"access_token": access_token, "token_type": "bearer"}
▶ Output
{"access_token": "eyJhbG...", "token_type": "bearer"}

Protecting Routes with Dependency Injection

To secure a route, we create a dependency that decodes the token. If the jwt.decode fails (token expired or tampered with), it raises an HTTPException. If successful, it returns the user object, which is then available to your route function.

io/thecodeforge/auth/dependencies.py · PYTHON
1234567891011121314151617181920
async def get_current_user(token: Annotated[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
    
    # Return a user dict or DB model
    return {"username": username, "active": True}

@app.get("/users/me")
async def read_users_me(current_user: Annotated[dict, Depends(get_current_user)]):
    return current_user
▶ Output
{"username": "forge_dev", "active": true}

🎯 Key Takeaways

  • OAuth2PasswordBearer is a built-in provider that extracts the token from the 'Authorization: Bearer ' header automatically.
  • The 'sub' (subject) claim in the JWT payload should uniquely identify the user (e.g., username or UUID).
  • Always use 'Annotated' for dependencies in modern FastAPI for better editor support and type safety.
  • Access tokens should be short-lived; use Refresh Tokens if users need to stay logged in for long periods without re-entering passwords.
  • Security is only as good as your SECRET_KEY—keep it high-entropy and strictly out of version control.

Interview Questions on This Topic

  • QExplain the 'Stateless' nature of JWT. Why does this benefit FastAPI's performance compared to Session-based auth?
  • QHow does the `Depends()` mechanism help in preventing code duplication for protected routes?
  • QScenario: A user's laptop is stolen. If we use standard JWTs, how would you 'blacklist' or revoke their current active token before it expires?
  • QWhat is the risk of using a weak `SECRET_KEY`, and how does it compromise the 'signature' part of the JWT?
  • QDescribe the flow of a 'Bearer' token from the client to the server. Which HTTP header is used?

Frequently Asked Questions

What is the difference between authentication and authorisation?

Authentication (AuthN) is the process of verifying that a user is who they say they are—essentially verifying their digital ID. Authorisation (AuthZ) happens after authentication and determines what specific resources or actions that verified user is permitted to access. In FastAPI, get_current_user usually handles AuthN, while scopes or role-checks handle AuthZ.

How do I implement refresh tokens in FastAPI?

To implement refresh tokens, you issue two tokens at login: an access_token (expires in minutes) and a refresh_token (expires in days/weeks). You create a /refresh endpoint that accepts the refresh token, validates it against your database (to ensure it hasn't been revoked), and issues a brand new access token. This allows for a seamless user experience while keeping the 'working' token's lifespan very short.

Is python-jose still the recommended library for FastAPI JWT?

While python-jose is widely used in documentation, many developers are moving to PyJWT or Authlib due to better maintenance. However, for standard OAuth2 implementation, python-jose remains the primary choice for FastAPI compatibility. Ensure you install with python-jose[cryptography] for secure signing.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousFastAPI Dependency Injection — How and Why to Use ItNext →FastAPI Background Tasks and Async Endpoints
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged