How to Secure a FastAPI Application with OAuth2 and JWT Authentication
In the rapidly evolving world of web development, security is paramount. FastAPI, a modern web framework for building APIs with Python, offers robust features, including the ability to secure applications using OAuth2 and JWT (JSON Web Tokens). In this article, we will explore how to implement these authentication methods in your FastAPI application, ensuring your users' data stays safe.
Understanding OAuth2 and JWT
What is OAuth2?
OAuth2 is an authorization framework that allows third-party services to exchange user information without sharing passwords. It provides a secure way for users to authorize applications to access their data.
Use Cases for OAuth2: - Granting limited access to user data. - Integrating third-party APIs securely. - Allowing users to sign in with existing accounts (like Google or Facebook).
What is JWT?
JWT is a compact and self-contained way to represent claims between two parties. It is commonly used in authentication mechanisms. A JWT is composed of three parts: Header, Payload, and Signature.
Use Cases for JWT: - Stateless authentication in web applications. - Secure data exchange between client and server. - Ensuring data integrity and authenticity.
Setting Up FastAPI
To start, you need a FastAPI application. If you haven't already, install FastAPI and an ASGI server like Uvicorn:
pip install fastapi uvicorn python-multipart
Now, let's create a basic FastAPI app:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
You can run this application with the following command:
uvicorn main:app --reload
Implementing OAuth2 with JWT Authentication
Step 1: Install Dependencies
In addition to FastAPI, you'll need passlib
for password hashing and python-jose
for handling JWTs. Install them using pip:
pip install passlib[bcrypt] python-jose
Step 2: Create User Models and Hashing Functions
First, define user models and a function to hash passwords:
from pydantic import BaseModel
from passlib.context import CryptContext
class User(BaseModel):
username: str
email: str
full_name: str = None
disabled: bool = None
class UserInDB(User):
hashed_password: str
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
Step 3: Setting Up OAuth2 with FastAPI
Use FastAPI’s built-in OAuth2PasswordBearer to manage the flow of authentication:
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
Step 4: Create Token and User Management
Now, let’s implement a simple user database and token generation:
from jose import JWTError, jwt
from datetime import datetime, timedelta
SECRET_KEY = "your_secret_key" # Change this
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": hash_password("secret"),
"disabled": False,
}
}
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 5: Authentication Endpoint
Create an endpoint to handle user authentication and token generation:
from fastapi import Depends, HTTPException, status
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = fake_users_db.get(form_data.username)
if not user or not verify_password(form_data.password, user['hashed_password']):
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 6: Protecting Routes
Now, implement a protected route that requires a valid token:
from fastapi import Security
@app.get("/users/me")
async def read_users_me(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
except JWTError:
raise credentials_exception
user = fake_users_db.get(username)
if user is None:
raise credentials_exception
return user
Conclusion
Securing your FastAPI application using OAuth2 and JWT authentication is crucial for protecting user data and ensuring a trusted environment. By following this step-by-step guide, you have learned how to set up user authentication, create tokens, and protect routes seamlessly.
Key Takeaways:
- OAuth2: Securely delegates access without sharing passwords.
- JWT: Provides a compact way to transmit information securely between parties.
- FastAPI: Simplifies the creation of secure APIs with built-in tools.
Implement these practices in your FastAPI applications to enhance security and build trust with your users. Happy coding!