securing-your-fastapi-application-with-oauth2-and-jwt.html

Securing Your FastAPI Application with OAuth2 and JWT

FastAPI is a modern web framework for building APIs with Python, known for its high performance and ease of use. As developers build more complex applications, securing these applications becomes paramount. One of the most effective ways to secure your FastAPI application is through OAuth2 and JSON Web Tokens (JWT). In this article, we will explore what OAuth2 and JWT are, how they work together, and provide a step-by-step guide to implementing them in your FastAPI application.

What is OAuth2?

OAuth2 is an authorization framework that enables third-party applications to obtain limited access to user accounts on an HTTP service. It allows users to grant access without sharing their passwords, enhancing security. OAuth2 is widely used by major platforms like Google, Facebook, and GitHub for user authentication.

Key Concepts of OAuth2

  • Authorization Server: The server that issues access tokens to the client after successfully authenticating the user.
  • Resource Server: The server that hosts the protected resources and requires access tokens for access.
  • Client: The application that requests access to the user's resources on the resource server.
  • Resource Owner: The user who owns the resources and grants access to the client.

What is JWT?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact way to securely transmit information between parties as a JSON object. It is used in authentication and information exchange due to its signature feature, which ensures the integrity of the data.

Structure of JWT

A JWT is composed of three parts:

  1. Header: Contains metadata about the token, including the type (JWT) and signing algorithm (e.g., HMAC SHA256).
  2. Payload: Contains the claims, which are statements about the user and additional data.
  3. Signature: The encoded header and payload are signed using a secret key to ensure the token's integrity.

Use Cases for OAuth2 and JWT in FastAPI

  • User Authentication: Allow users to log in using their credentials securely.
  • Role-Based Access Control: Implement different access levels based on user roles.
  • Third-Party Authentication: Enable users to log in with their social media accounts.
  • Mobile Application Security: Secure mobile applications by implementing token-based authentication.

Implementing OAuth2 and JWT in FastAPI

Step 1: Setting Up Your FastAPI Project

First, create a new FastAPI project. Make sure you have FastAPI and the uvicorn ASGI server installed. You can install them using pip:

pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt]

Step 2: Creating the FastAPI Application

Create a file named main.py and start by setting up a basic FastAPI application with user authentication logic.

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta

# Constants
SECRET_KEY = "your_secret_key"  # Change this to a strong secret key
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# App and security instances
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# User database simulation
fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": pwd_context.hash("password"),
        "disabled": False,
    }
}

# Pydantic models
class User(BaseModel):
    username: str
    email: str | None = None
    full_name: str | None = None
    disabled: bool | None = None

class UserInDB(User):
    hashed_password: str

class Token(BaseModel):
    access_token: str
    token_type: str

class TokenData(BaseModel):
    username: str

# Utility functions
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)

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

Step 3: Creating Authentication Endpoints

Now we will implement the endpoints for user login and token retrieval.

@app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = get_user(fake_users_db, form_data.username)
    if not user or not verify_password(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

Step 4: Protecting Routes with JWT

To protect certain routes, you can create a dependency that verifies the JWT.

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")
        if username is None:
            raise credentials_exception
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    return user

@app.get("/users/me", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_user)):
    return current_user

Step 5: Running Your Application

To run your FastAPI application, use the following command:

uvicorn main:app --reload

You can now access the /docs endpoint to see the interactive API documentation provided by FastAPI.

Conclusion

Securing your FastAPI application with OAuth2 and JWT is a straightforward process that significantly enhances the security of your application. By implementing the steps outlined in this article, you can create a robust authentication system that allows users to securely access your resources. As you continue developing your application, consider implementing additional security measures such as HTTPS, user roles, and logging for a comprehensive security posture.

With FastAPI's capabilities and the power of OAuth2 and JWT, you're well on your way to building secure and scalable applications. Happy coding!

SR
Syed
Rizwan

About the Author

Syed Rizwan is a Machine Learning Engineer with 5 years of experience in AI, IoT, and Industrial Automation.