7-creating-a-secure-fastapi-application-with-jwt-authentication.html

Creating a Secure FastAPI Application with JWT Authentication

FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. One of its prominent features is the ability to implement security protocols, such as JWT (JSON Web Tokens) authentication, which is essential for protecting your application and managing user sessions effectively. In this article, we will delve into how to create a secure FastAPI application using JWT authentication, providing clear code examples, step-by-step instructions, and actionable insights.

What is JWT Authentication?

JWT, or JSON Web Token, is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

Why Use JWT?

  • Stateless Authentication: JWT allows for stateless authentication, meaning that the server does not need to store session information.
  • Compact: The compact size of JWT makes it easy to transfer over the URL, POST parameters, or inside an HTTP header.
  • Cross-Domain: JWT can be used across different domains, making them ideal for microservices architectures.

Setting Up Your FastAPI Application

To get started, we need to set up a basic FastAPI application. If you haven't installed FastAPI and its dependencies yet, you can do so using pip:

pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt]
  • uvicorn is an ASGI server for running FastAPI applications.
  • python-jose is used for creating and verifying JWT tokens.
  • passlib is a password hashing library to securely manage user passwords.

Basic FastAPI Application Structure

Create a new directory for your application and add a file named main.py. Below is the structure of your FastAPI application:

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

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

# FastAPI instance
app = FastAPI()

# Models
class User(BaseModel):
    username: str
    email: str
    full_name: str = None
    disabled: bool = None

class UserInDB(User):
    hashed_password: str

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

class TokenData(BaseModel):
    username: str

# Password hashing context
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# OAuth2 Password Bearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

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

Implementing JWT Authentication

Now that we have the basic structure in place, let's implement JWT authentication.

Step 1: Create a Token

We need a function to create JWT tokens:

def create_access_token(data: dict, expires_delta: timedelta = 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 2: Authenticate Users

Next, we’ll create a function to verify user credentials:

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 authenticate_user(db, username: str, password: str):
    user = get_user(db, username)
    if not user or not verify_password(password, user.hashed_password):
        return False
    return user

Step 3: Create the Token Endpoint

Now, we can set up the /token endpoint to authenticate users and return a JWT:

@app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        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

To protect specific routes, we can create a dependency that will require a valid token:

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

Secure a Protected Route

Now, let’s create a protected route that only authenticated users can access:

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

Running the Application

You can run your FastAPI application using Uvicorn:

uvicorn main:app --reload

Conclusion

In this article, we covered how to create a secure FastAPI application using JWT authentication. By implementing JWT, you can ensure that your application is protected against unauthorized access, providing a better user experience. With the provided code snippets, you can easily set up your own FastAPI application with secure user authentication.

Feel free to expand upon this foundation by integrating a real database, improving error handling, and optimizing your security measures. 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.