JSON Web Tokens (JWT) are a popular method for implementing authentication and authorization in modern web applications. FastAPI, a high-performance Python web framework, seamlessly integrates with JWT to secure APIs efficiently. If you're looking to add JWT-based authentication to your FastAPI project, this comprehensive guide will walk you through the entire process—from installation to implementation. By the end of this tutorial, you'll have a robust JWT authentication system in place for your FastAPI application.
Prerequisites and Setup
Before diving into JWT integration, ensure you have a working FastAPI project and a Python environment set up. If you're starting from scratch, follow these steps:
- Install Python 3.7+ on your system.
- Create a new virtual environment for your project:
python -m venv env
source env/bin/activate # On Windows use: env\Scripts\activate
pip install fastapi uvicorn
With these prerequisites met, you're ready to proceed with JWT integration.
Step 1: Install Required Libraries
FastAPI doesn't include JWT functionality out of the box. You'll need to install additional libraries that facilitate JWT encoding, decoding, and security handling. The most commonly used packages are:
- PyJWT: A Python library for creating and verifying JWT tokens.
- passlib: For hashing passwords securely.
- python-multipart: To handle form data, if needed.
Run the following command to install these libraries:
pip install PyJWT passlib[bcrypt] python-multipart
Here's a brief overview of these libraries:
- PyJWT: Handles JWT token creation and validation.
- passlib[bcrypt]: Provides secure password hashing, essential for user authentication.
- python-multipart: Enables handling of multipart form data, useful for login forms.
Step 2: Set Up User Authentication Models and Utilities
To implement JWT authentication, you need user models and utility functions for password hashing and verification. Here's a simple example:
from passlib.context import CryptContext
# Create a password context for hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# User data example (In real scenarios, use a database)
fake_users_db = {
"alice": {
"username": "alice",
"full_name": "Alice Wonderland",
"hashed_password": pwd_context.hash("secret"),
"disabled": False,
}
}
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def get_user(username: str):
user = fake_users_db.get(username)
return user
This setup provides basic password handling. For production, integrate with a database like PostgreSQL or MySQL.
Step 3: Create JWT Utility Functions
Next, define functions to generate and verify JWT tokens. Use the jwt library from PyJWT:
import jwt
from datetime import datetime, timedelta
# Secret key for encoding and decoding JWT
SECRET_KEY = "your_secret_key" # Replace with a secure key
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
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
def decode_access_token(token: str):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
return None
return payload
except jwt.PyJWTError:
return None
Replace your_secret_key with a secure, randomly generated key, especially for production environments.
Step 4: Define FastAPI Dependencies for Authentication
FastAPI uses dependency injection to handle authentication. Create a function to extract and verify JWT tokens from request headers:
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
payload = decode_access_token(token)
if payload is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
username: str = payload.get("sub")
user = get_user(username)
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found",
headers={"WWW-Authenticate": "Bearer"},
)
return user
This function verifies the token and retrieves the user info, enabling protected routes.
Step 5: Create Authentication Endpoints
Implement a login route that authenticates users and returns a JWT token:
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/token")
async def login_for_access_token(
username: str = Form(...),
password: str = Form(...)
):
user = get_user(username)
if not user:
raise HTTPException(status_code=400, detail="Incorrect username or password")
if not verify_password(password, user["hashed_password"]):
raise HTTPException(status_code=400, detail="Incorrect username or password")
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"}
This endpoint accepts username and password, authenticates the user, and returns a JWT token.
Step 6: Protect Routes with JWT Authentication
Use the get_current_user dependency to secure your API endpoints:
@app.get("/users/me")
async def read_users_me(current_user: dict = Depends(get_current_user)):
return current_user
This route will only be accessible with a valid JWT token provided in the Authorization header.
Full Example: Combining All Components
Here's a simplified but complete FastAPI app demonstrating JWT login and protected route:
from fastapi import FastAPI, Depends, HTTPException, status, Form
from fastapi.security import OAuth2PasswordBearer
import jwt
from datetime import datetime, timedelta
from passlib.context import CryptContext
app = FastAPI()
# Password hashing setup
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Fake user database
fake_users_db = {
"alice": {
"username": "alice",
"full_name": "Alice Wonderland",
"hashed_password": pwd_context.hash("secret"),
"disabled": False,
}
}
# JWT configuration
SECRET_KEY = "your_secret_key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_user(username: str):
user = fake_users_db.get(username)
return user
def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
def decode_access_token(token: str):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
return None
return payload
except jwt.PyJWTError:
return None
async def get_current_user(token: str = Depends(oauth2_scheme)):
payload = decode_access_token(token)
if payload is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
username: str = payload.get("sub")
user = get_user(username)
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found",
headers={"WWW-Authenticate": "Bearer"},
)
return user
@app.post("/token")
async def login_for_access_token(
username: str = Form(...),
password: str = Form(...)
):
user = get_user(username)
if not user or not verify_password(password, user["hashed_password"]):
raise HTTPException(status_code=400, detail="Incorrect username or password")
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"}
@app.get("/users/me")
async def read_users_me(current_user: dict = Depends(get_current_user)):
return current_user
This example provides a full JWT authentication flow in FastAPI, from login to accessing protected routes.
Best Practices and Tips
- Secure Your Secret Key: Use environment variables or secret management tools to store your JWT secret key securely. Never hardcode in production code.
- Token Expiration: Set appropriate expiration times for tokens to minimize security risks.
- Refresh Tokens: Implement refresh tokens for longer sessions, allowing users to obtain new tokens without re-authentication.
- HTTPS: Always serve your API over HTTPS, especially when transmitting tokens, to prevent man-in-the-middle attacks.
- Validate Tokens: Always verify token signatures and expiration before granting access.
Conclusion
Integrating JWT authentication into your FastAPI application enhances security by providing stateless, scalable user authentication. The process involves installing necessary libraries, setting up user models and password hashing, creating JWT utility functions, and protecting routes with dependencies. While this guide covers the basics, remember to follow best security practices for production deployments, such as securing your secret keys, using HTTPS, and implementing refresh tokens. By mastering JWT in FastAPI, you can build secure, efficient, and scalable APIs suitable for modern web applications.
Disclaimer: Articles are written by Humans, AI or Both. Verify Important information.