How to Set Up a Secure FastAPI Application with OAuth2 Authentication
FastAPI is a modern, high-performance web framework for building APIs with Python 3.6+. It is designed to create RESTful APIs quickly and efficiently while ensuring security and ease of use. One of the common methods to secure your FastAPI application is through OAuth2 authentication. In this article, we’ll walk through the process of setting up a secure FastAPI application with OAuth2, covering its definitions, use cases, and actionable insights.
What is OAuth2?
OAuth2 (Open Authorization 2.0) is an industry-standard protocol that allows third-party applications to access a user’s resources without exposing their credentials. Instead of sharing passwords, OAuth2 enables users to authorize applications using tokens, ensuring a more secure and user-friendly experience.
Use Cases of OAuth2
- Mobile Applications: Allow users to log in using their social media accounts without needing to create new credentials.
- API Access: Securely grant limited access to APIs for different applications or services.
- Single Sign-On (SSO): Enable users to authenticate once and gain access to multiple applications.
Setting Up FastAPI with OAuth2
Prerequisites
Before we dive into the code, ensure you have the following installed:
- Python 3.6 or higher
- FastAPI
- Uvicorn (for running the server)
python-jose
(for JWT token handling)passlib
(for password hashing)
You can install the required packages using pip:
pip install fastapi[all] python-jose[cryptography] passlib
Step 1: Basic FastAPI Setup
First, create a new FastAPI application. Create a file named main.py
and add the following code:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Welcome to FastAPI with OAuth2!"}
Run the application using Uvicorn:
uvicorn main:app --reload
Now, navigate to http://127.0.0.1:8000
in your web browser to see the welcome message.
Step 2: Implementing OAuth2
To implement OAuth2, we will use the password flow for this example. This allows users to log in with their username and password, receiving a token in return.
Setting Up User Models
Create a file named models.py
and define a User
and UserInDB
model:
from pydantic import BaseModel
class User(BaseModel):
username: str
email: str
class UserInDB(User):
hashed_password: str
Token Creation
Next, create a utility function to generate JWT tokens. Add this to a new file named auth.py
:
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
# Constants
SECRET_KEY = "your_secret_key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
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 verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
User Registration and Login
Add the user registration and login routes in main.py
:
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
from datetime import timedelta
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Simulated user database
fake_users_db = {
"johndoe": {
"username": "johndoe",
"email": "johndoe@example.com",
"hashed_password": get_password_hash("password123"),
}
}
@app.post("/token")
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 3: Protecting Routes with OAuth2
Now, let’s protect a route using the OAuth2 token. In main.py
, add the following:
@app.get("/users/me")
def read_users_me(token: str = Depends(oauth2_scheme)):
# Here you would decode the token to get the user information
return {"token": token}
Step 4: Testing the Application
- Start the FastAPI application.
- To obtain a token, make a POST request to
http://127.0.0.1:8000/token
with the username and password in the body using a tool like Postman or cURL. - Use the returned token to access the protected route
http://127.0.0.1:8000/users/me
.
Troubleshooting Common Issues
- Invalid Token: Ensure the token is correctly generated and passed in the
Authorization
header. - User Not Found: Check the user registration process and ensure the credentials match.
- Security Issues: Always use HTTPS to secure token transmission.
Conclusion
Setting up a secure FastAPI application with OAuth2 authentication enhances the security of your API. By following the steps outlined in this article, you can implement a robust authentication mechanism, safeguarding user data effectively. FastAPI's simplicity combined with OAuth2’s security makes it an ideal choice for modern web applications. Whether you’re building a small project or scaling up to a full-fledged API service, FastAPI and OAuth2 provide the tools you need to succeed. Happy coding!