Building Secure API Endpoints with FastAPI and JWT Authentication
In today's digital landscape, creating secure and efficient APIs is essential for modern web applications. FastAPI has emerged as one of the most popular frameworks for building APIs in Python, thanks to its speed, simplicity, and support for asynchronous programming. Coupled with JSON Web Tokens (JWT) for authentication, FastAPI provides a robust solution for securing your API endpoints. In this article, we'll explore how to build secure API endpoints using FastAPI and JWT authentication, providing you with actionable insights and clear code examples.
Understanding FastAPI and JWT
What is FastAPI?
FastAPI is a modern web framework for building APIs in Python 3.6 and later. It is designed to be fast, with automatic generation of OpenAPI documentation, support for asynchronous programming, and easy integration with other libraries. Its type hinting feature enhances code readability and facilitates validation.
What is JWT?
JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure. JWT is widely used for authentication and information exchange securely.
Use Cases for FastAPI and JWT
- User Authentication: Secure user login and access to resources.
- Microservices: Authenticate requests between microservices securely.
- Single Page Applications (SPAs): Manage user sessions in SPAs efficiently.
Setting Up Your Environment
Before diving into the code, ensure you have Python and FastAPI installed. You can set up a virtual environment and install the necessary packages using pip.
# Create a virtual environment
python -m venv fastapi-env
source fastapi-env/bin/activate # On Windows use `fastapi-env\Scripts\activate`
# Install FastAPI and an ASGI server (e.g., uvicorn)
pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt]
Required Packages
- FastAPI: For building API endpoints.
- uvicorn: For serving the application.
- python-jose: For creating and verifying JWTs.
- passlib: For hashing passwords securely.
Building the FastAPI Application
Step 1: Create the Main Application File
Create a file named main.py
and start building your FastAPI application.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from typing import List
from passlib.context import CryptContext
import jwt
import datetime
app = FastAPI()
# Security settings
SECRET_KEY = "your_secret_key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# OAuth2 scheme
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Sample user database
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": pwd_context.hash("password"),
"disabled": False,
}
}
# Pydantic models
class User(BaseModel):
username: str
email: str
full_name: str = None
disabled: bool = None
class UserInDB(User):
hashed_password: str
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: str
Step 2: Create User Authentication Logic
Next, implement the functions to authenticate users and create JWT tokens.
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
def create_access_token(data: dict, expires_delta: datetime.timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.datetime.utcnow() + expires_delta
else:
expire = datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
Step 3: Create the Token Endpoint
Now, add an endpoint to handle user login and token issuance.
@app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = get_user(fake_users_db, 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 = datetime.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 4: Protecting API Endpoints
Now that we can issue tokens, let’s create a protected endpoint that requires a valid JWT.
@app.get("/users/me", response_model=User)
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
token_data = TokenData(username=username)
except jwt.PyJWTError:
raise credentials_exception
user = get_user(fake_users_db, token_data.username)
if user is None:
raise credentials_exception
return user
Step 5: Running the Application
To run your FastAPI application, use the following command:
uvicorn main:app --reload
Step 6: Testing the API
Use tools like Postman or CURL to test your API endpoints. First, obtain a JWT token by sending a POST request to /token
with the username and password. Then, use this token to access the protected /users/me
endpoint.
Conclusion
Building secure API endpoints with FastAPI and JWT authentication is a powerful way to protect your applications. With FastAPI’s simplicity and the robustness of JWT, you can easily implement user authentication and safeguard your resources.
This guide provided the foundational steps for creating a secure API. As you further develop your application, consider integrating additional features like user registration, password reset functionality, and more sophisticated user management.
By following these practices, you enhance your API's security while maintaining a smooth user experience. Happy coding!