Mastering FastAPI Security: A Comprehensive Guide to OAuth2 with Password Hashing and JWT Bearer Tokens
In the age of digital transformation, securing web applications has become paramount. FastAPI is one of the most popular frameworks for building APIs with Python, and it provides several options for securing endpoints and managing authentication. This comprehensive guide will walk you through implementing OAuth2 with password hashing and JWT bearer tokens in FastAPI.
Introduction to OAuth2
OAuth2 is a widely adopted authorization framework that allows applications to obtain limited access to user accounts on an HTTP service. It works through the delegation of user authentication to a third-party service, enabling more secure and efficient access control.
Key Concepts of OAuth2
- Resource Owner: The user who authorizes an application to access their account.
- Client: The application requesting access to the user's account.
- Authorization Server: The server that issues tokens after successfully authenticating the Resource Owner and obtaining authorization.
- Resource Server: The server that houses the user's data and accepts access tokens for resource access.
Setting Up FastAPI for OAuth2
We'll start by setting up a basic FastAPI application and adding the necessary libraries for implementing OAuth2.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from passlib.context import CryptContext
from jose import JWTError, jwt
from pydantic import BaseModel
from datetime import datetime, timedelta
from typing import Optional
The libraries and modules you see here are essential for handling dependencies, HTTP exceptions, password hashing, and JSON Web Tokens (JWT).
Implementing Password Hashing
Storing plain text passwords is never a good practice. Instead, we hash passwords using passlib
, a password hashing library for Python.
Creating a Password Hashing Function
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
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)
Here, verify_password
checks if the plain text password matches the hashed password, while get_password_hash
hashes a plain text password.
Creating Models and Token Utilities
We need user models and utilities for generating and decoding JWT tokens.
User and Token Models
class User(BaseModel):
username: str
class UserInDB(User):
hashed_password: str
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
User
represents a user, UserInDB
includes the hashed password, and Token
and TokenData
deal with JWT tokens.
JWT Utilities
SECRET_KEY = "secret"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
def create_access_token(data: dict, expires_delta: Optional[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
The create_access_token
function generates JWT tokens with an expiration time.
Handling Authentication and Authorization
We'll use the OAuth2PasswordBearer and OAuth2PasswordRequestForm classes to handle authentication.
Dependency Injection for Secure Endpoints
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="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
This code validates the JWT token and retrieves the current user from the token data.
Creating Routes for Token Generation and Secure Access
app = FastAPI()
@app.post("/token", response_model=Token)
async def login_for_access_token(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"}
@app.get("/users/me", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
The /token
endpoint generates a JWT token, and /users/me
returns the current authenticated user's data.
Conclusion
Implementing OAuth2 with password hashing and JWT bearer tokens in FastAPI provides robust security for your APIs. This guide covered the essential components, including setting up FastAPI, implementing password hashing, creating JWT tokens, and securing endpoints. By following these steps, you can enhance the security of your applications while leveraging FastAPI's efficiency.
Ready to take your FastAPI application to the next level? Start implementing these security practices today to safeguard your users and data.