Page cover image

🥑Unit 18: Implementing JWT Authentication with FastAPI

Introduction

JWT (JSON Web Token) is a compact, secure means for transmitting information as a JSON object. It's commonly used for authentication and authorization purposes.

What is JWT?

JWT is composed of three parts:

  • Header: Specifies the algorithm used to sign the token.

  • Payload: Contains claims, including user details and token expiration.

  • Signature: Used to verify token authenticity.

Advantages of JWT

  • Stateless authentication

  • Secure information exchange

  • Easy integration with frontend frameworks

Implementing JWT in FastAPI

Step-by-step Implementation:

Step 1: Install Dependencies

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

Step 2: Basic JWT Setup

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

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

app = FastAPI()

# Dummy user
fake_users_db = {
    "[email protected]": {
        "username": "[email protected]",
        "hashed_password": pwd_context.hash("password123")
    }
}

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

def authenticate_user(username: str, password: str):
    user = fake_users_db.get(username)
    if not user or not verify_password(password, user["hashed_password"]):
        return False
    return user

@app.post("/token")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    token_data = {"sub": user["username"]}
    access_token = jwt.encode(token_data, SECRET_KEY, algorithm=ALGORITHM)
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/protected")
def read_protected(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    except JWTError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    return {"Hello": username}

Testing the Application

You can test the above program with Postman, or you can use the following testing approach with pytest and httpx.

Create a test file test_jwt.py:

import pytest
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_login_and_protected_endpoint():
    response = client.post("/token", data={"username": "[email protected]", "password": "password123"})
    assert response.status_code == 200
    token = response.json()["access_token"]

    protected_response = client.get("/protected", headers={"Authorization": f"Bearer {token}"})
    assert protected_response.status_code == 200
    assert protected_response.json() == {"Hello": "[email protected]"}

def test_invalid_token():
    protected_response = client.get("/protected", headers={"Authorization": "Bearer invalidtoken"})
    assert protected_response.status_code == 401

Run the tests with:

pytest test_jwt.py

Exercises

  1. Extend JWT Payload: Modify the JWT to include user roles (admin, user) and restrict access to specific endpoints based on these roles.

  2. Implement Token Expiration: Set JWT expiration to 30 minutes and handle token refresh.

  3. JWT Blacklisting: Implement JWT token blacklisting using a Redis cache to enable immediate token revocation.

  4. Testing and Validation: Write comprehensive unit tests using Pytest to test JWT authentication scenarios, including edge cases such as invalid and expired tokens.

Last updated