Implementing JWT Authentication in FastAPI

FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints. JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims to be transferred between two parties. Implementing JWT authentication in FastAPI provides a secure and efficient way to handle user authentication and authorization in web applications. This blog will guide you through the fundamental concepts, usage methods, common practices, and best practices of implementing JWT authentication in FastAPI.

Table of Contents

  1. Fundamental Concepts
  2. Setting Up a FastAPI Project
  3. Generating and Verifying JWT Tokens
  4. Protecting Routes with JWT Authentication
  5. Common Practices
  6. Best Practices
  7. Conclusion
  8. References

Fundamental Concepts

What is JWT?

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. A JWT consists of three parts: a header, a payload, and a signature. The header typically contains information about the token type and the signing algorithm. The payload contains the claims, which can be either registered, public, or private claims. The signature is used to verify that the sender of the JWT is who it claims to be and to ensure that the message wasn’t changed along the way.

Why Use JWT in FastAPI?

  • Stateless: JWTs are stateless, which means that the server doesn’t need to store any session information. This makes it easier to scale the application horizontally.
  • Portable: JWTs can be easily passed between different services and applications, making them suitable for microservices architectures.
  • Secure: JWTs can be signed and encrypted, ensuring the integrity and confidentiality of the data.

Setting Up a FastAPI Project

First, make sure you have Python installed on your system. Then, create a virtual environment and install FastAPI and other necessary libraries:

# Create a virtual environment
python -m venv venv

# Activate the virtual environment
# On Windows
venv\Scripts\activate
# On Linux/Mac
source venv/bin/activate

# Install FastAPI and Uvicorn
pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt]

Here is a simple FastAPI application structure:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

To run the application, use the following command:

uvicorn main:app --reload

Generating and Verifying JWT Tokens

Generate a Secret Key

You need a secret key to sign and verify JWT tokens. You can generate a random secret key using the following Python code:

import secrets

SECRET_KEY = secrets.token_urlsafe(32)
print(SECRET_KEY)

Generate a JWT Token

Here is an example of generating a JWT token in FastAPI:

from fastapi import Depends, FastAPI
from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Optional

# Secret key and algorithm
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

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

app = FastAPI()

@app.post("/token")
def login():
    data = {"sub": "user123"}
    access_token = create_access_token(data)
    return {"access_token": access_token, "token_type": "bearer"}

Verify a JWT Token

def verify_token(token: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        return username
    except JWTError:
        raise credentials_exception

Protecting Routes with JWT Authentication

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

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

app = FastAPI()

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

def verify_token(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=401,
        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
        return username
    except JWTError:
        raise credentials_exception

@app.post("/token")
def login():
    data = {"sub": "user123"}
    access_token = create_access_token(data)
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/protected")
def protected_route(username: str = Depends(verify_token)):
    return {"message": f"Hello, {username}! This is a protected route."}

Common Practices

  • Store Secrets Securely: Keep your secret key and other sensitive information in environment variables or a secure secrets management system.
  • Use HTTPS: Always use HTTPS to protect the transmission of JWT tokens between the client and the server.
  • Set Token Expiration: Set an appropriate expiration time for your JWT tokens to limit the time window for potential attacks.

Best Practices

  • Refresh Tokens: Implement a refresh token mechanism to allow users to obtain new access tokens without having to re-authenticate.
  • Token Revocation: Have a mechanism to revoke tokens in case of security breaches or when a user logs out.
  • Input Validation: Validate all user input, including JWT tokens, to prevent injection attacks.

Conclusion

Implementing JWT authentication in FastAPI provides a secure and efficient way to handle user authentication and authorization. By understanding the fundamental concepts, following common practices, and implementing best practices, you can build robust and secure web applications. JWTs are well-suited for modern web architectures, such as microservices, due to their stateless and portable nature.

References