Creating a Secure API Using FastAPI with JWT Authentication
In today's digital landscape, securing APIs is paramount. Whether you're building a web application, a mobile app, or an IoT solution, ensuring that your API is secure protects your data and your users. FastAPI, an increasingly popular web framework for building APIs in Python, offers built-in support for various authentication methods, including JSON Web Tokens (JWT). In this article, we will explore how to create a secure API using FastAPI with JWT authentication, providing you with a step-by-step guide, code examples, and actionable insights.
Understanding JWT Authentication
What is JWT?
JSON Web Tokens (JWT) are an open standard for securely transmitting information between parties as a JSON object. These tokens can be verified and trusted because they are digitally signed. JWTs are commonly used for authentication and information exchange in web applications.
Why Use JWT?
- Statelessness: JWTs are self-contained and do not require session storage on the server.
- Compact: They are URL-safe and can be easily transmitted in HTTP headers.
- Versatile: JWTs can carry different types of information, making them suitable for various use cases like user authentication and authorization.
Setting Up FastAPI
Before we dive into JWT authentication, let’s set up a basic FastAPI application. If you haven't already, install FastAPI and an ASGI server like uvicorn
.
pip install fastapi uvicorn python-jose[cryptography]
Creating a Basic FastAPI Application
Create a file called main.py
and add the following code to set up a simple FastAPI application:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Welcome to FastAPI!"}
Run your application using the following command:
uvicorn main:app --reload
Now, when you navigate to http://127.0.0.1:8000
, you should see the welcome message.
Implementing JWT Authentication
Step 1: User Registration and Login
To implement JWT authentication, we need a way for users to register and log in. Let's create a simple in-memory user store for demonstration purposes.
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from typing import List
from passlib.context import CryptContext
import jwt
import datetime
# Constants
SECRET_KEY = "your_secret_key" # Change this in production
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# User model
class User(BaseModel):
username: str
email: str | None = None
class UserInDB(User):
hashed_password: str
# In-memory user store
fake_users_db = {}
# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# FastAPI app
app = FastAPI()
# OAuth2 scheme
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Endpoint for user registration
@app.post("/register", response_model=User)
async def register(user: UserInDB):
if user.username in fake_users_db:
raise HTTPException(status_code=400, detail="Username already registered")
user.hashed_password = pwd_context.hash(user.hashed_password)
fake_users_db[user.username] = user
return user
# Endpoint for user login
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = fake_users_db.get(form_data.username)
if not user or not pwd_context.verify(form_data.password, user.hashed_password):
raise HTTPException(status_code=401, detail="Invalid credentials")
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"}
# Function to create a JWT token
def create_access_token(data: dict, expires_delta: datetime.timedelta | None = 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})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
Step 2: Protecting Endpoints with JWT
Now that we have user registration and login functionality, we can secure our API endpoints using JWT.
from fastapi import Security
# Function to get the current user
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=401,
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
return fake_users_db.get(username)
except jwt.PyJWTError:
raise credentials_exception
# Protected endpoint
@app.get("/users/me", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
Step 3: Testing the API
- Register a User: Send a POST request to
/register
with a JSON body containingusername
,email
, andhashed_password
(hashed using bcrypt). - Login to Get Token: Send a POST request to
/token
with form data containingusername
andpassword
. You will receive a JWT in response. - Access Protected Endpoint: Use the JWT as a Bearer token in the Authorization header to access the
/users/me
endpoint.
Conclusion
Securing your API with JWT authentication in FastAPI is a straightforward process that enhances the security of your applications. By following this guide, you can create a robust authentication system that protects your data and user information. FastAPI's ease of use, combined with the power of JWT, makes it an excellent choice for modern web applications.
Key Takeaways
- Use FastAPI to create APIs quickly and efficiently.
- Implement JWT for stateless authentication.
- Secure endpoints by validating JWTs.
With these tools in your arsenal, you're well-equipped to build secure, high-performance APIs. Happy coding!