How to Add Authentication in FastAPI: A Complete Guide
Securing your web API is essential in modern software development. FastAPI makes building APIs simple, but adding robust authentication ensures that your endpoints are protected. This tutorial walks you through how to implement authentication in FastAPI using OAuth2 password flow with JSON Web Tokens (JWT).
Prerequisites
- Basic knowledge of Python and FastAPI
- Python installed (3.7+ recommended)
- Familiarity with REST API concepts
- Package manager pip
Step 1: Setting Up Your FastAPI Project
If you haven’t already installed FastAPI and Uvicorn (ASGI server), start by running:
pip install fastapi uvicorn passlib[bcrypt] python-jose
Here, passlib[bcrypt] is used for hashing passwords securely, and python-jose handles JWT token creation and verification.
Step 2: Creating User Authentication Dependencies
First, let’s import the required modules and create a simple “database” of users for demonstration.
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Optional
# Secret key to encode and decode JWT tokens
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
fake_users_db = {
"alice": {
"username": "alice",
"full_name": "Alice Wonderland",
"email": "[email protected]",
"hashed_password": pwd_context.hash("secret"),
"disabled": False,
}
}
app = FastAPI()
Helper Functions
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 user_dict
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
return False
if not verify_password(password, user["hashed_password"]):
return False
return user
Step 3: Creating JWT Access Tokens
We create tokens that clients will use to access protected endpoints.
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
Step 4: Defining the Token Endpoint
Clients will POST username and password to this endpoint to receive JWT tokens.
@app.post("/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"}
Step 5: Creating a Dependency to Get the Current User
This ensures protected routes can verify the requester’s identity and active status.
from fastapi import Security
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
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user: dict = Security(get_current_user)):
if current_user.get("disabled"):
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
Step 6: Protecting Routes with Authentication
Use the dependency to secure any route you want.
@app.get("/users/me")
async def read_users_me(current_user: dict = Depends(get_current_active_user)):
return current_user
Testing Your Authentication
- Start your FastAPI server:
uvicorn main:app --reload - Visit the interactive docs at
http://127.0.0.1:8000/docs - Use the “/token” endpoint to get an access token by providing username and password (e.g., alice / secret)
- Authorize in Swagger UI with the token (copy “Bearer <token>”) and try the protected endpoint “/users/me”
Troubleshooting
- Authentication errors: Check that your SECRET_KEY is complex and consistent across runs.
- Expired tokens: Tokens expire after 30 minutes; request new ones as needed.
- Wrong username/password: Verify credentials match your hashed passwords using bcrypt.
Summary Checklist
- Installed necessary packages
- Created user authentication helpers
- Implemented JWT token creation and verification
- Created OAuth2 token endpoint
- Protected routes using token-based authentication
- Tested with FastAPI interactive docs
This tutorial builds upon foundational FastAPI concepts. For more API development tips in FastAPI, you can check our related tutorial on how to create endpoints in FastAPI.
With this authentication setup, your API can now securely verify users and protect sensitive data effectively.
