🥑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
Extend JWT Payload: Modify the JWT to include user roles (admin, user) and restrict access to specific endpoints based on these roles.
Implement Token Expiration: Set JWT expiration to 30 minutes and handle token refresh.
JWT Blacklisting: Implement JWT token blacklisting using a Redis cache to enable immediate token revocation.
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