Hello! Welcome back to our course on FastAPI.
In our last lesson, you successfully built a user registration endpoint. You learned how to take user credentials, securely hash the password using bcrypt, and store the user in the database. This was a critical first step in building a secure API.
Today, we'll build the other side of that coin: the login endpoint. This is where a user will exchange their credentials for a JSON Web Token (JWT). This token will then be used to access protected parts of our application.
By the end of this lesson, you will have a fully functional /login (or /token) endpoint that:
- Accepts a username and password.
- Verifies these credentials against the stored hashed password.
- Issues a signed JWT access token upon successful verification.
This brings us one step closer to your goal of building secure, production-ready APIs for your AI models.
Step 1: Installing Dependencies and Initial Configuration
To create and manage JWTs, we need a couple of libraries. We also need a way to configure our security settings, like the secret key that signs the tokens.
First, let's install the required packages. python-jose is a library for handling JWTs, and python-multipart is needed by FastAPI to handle the form data from the login request.
pip install "python-jose[cryptography]" python-multipart
Now, let's create a file to hold our authentication logic and configuration. In the previous lesson, we created auth_utils.py. Let's add our JWT configuration there. For a real application, you would load these from environment variables, but for now, we'll define them as constants.
Open your auth_utils.py and add the following at the top:
# In auth_utils.py
from datetime import datetime, timedelta, timezone
from typing import Optional
from jose import jwt, JWTError
from passlib.context import CryptContext
# Security constants
SECRET_KEY = "a_super_secret_key_that_should_be_in_an_env_file" # IMPORTANT: Change this and keep it secret
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# --- Keep your existing pwd_context and hashing functions here ---
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
# ... existing function
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
# ... existing function
return pwd_context.verify(plain_password, hashed_password)
SECRET_KEY: This is the secret string used to sign our JWTs. Only our server knows this key, which is how we can verify that the tokens we receive haven't been tampered with.ALGORITHM: The specific algorithm used for signing.HS256is a common and secure choice.ACCESS_TOKEN_EXPIRE_MINUTES: How long a token is valid after it's been issued.
Step 2: Building the Authentication Helper Functions
Just like we created helpers for hashing passwords, we'll create helpers for authenticating a user and creating their token. This keeps our code clean and reusable.
Let's add two new functions to auth_utils.py.
1. authenticate_user
This function will check if a user exists and if the provided password is correct. It will use the verify_password function you created in the last lesson.
# Add this to auth_utils.py, and update your imports in that file
from sqlalchemy.orm import Session
from . import crud # Assuming crud.py is in the same directory
# ... (rest of auth_utils.py)
def authenticate_user(db: Session, username: str, password: str):
"""
Authenticates a user.
- Fetches the user by username.
- Verifies the provided password against the stored hash.
- Returns the user object on success, otherwise returns False.
"""
user = crud.get_user_by_username(db, username=username)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user
This function is the core of our credential validation logic. Notice it returns a generic False whether the user doesn't exist or the password is wrong. This is a security measure to prevent attackers from figuring out which usernames are registered in the system (a "user enumeration" attack).
2. create_access_token
This function will generate the JWT. It takes the data to be encoded (the payload) and an optional expiration time.
# Add this to auth_utils.py
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
"""
Creates a JWT access token.
- Copies the payload data.
- Sets an expiration time.
- Encodes the token using the secret key and algorithm.
"""
to_encode = data.copy()
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
The payload is a dictionary. The "sub" (subject) claim is a standard way to identify who the token is for—we'll use the username. The "exp" (expiration time) claim is crucial for security, ensuring tokens don't live forever.
For a detailed walkthrough of setting up these utilities, the following video is a great resource.
FastAPI JWT Tutorial | How to add User Authentication
The video "FastAPI JWT Tutorial" by Eric Roby clearly demonstrates how to set up the necessary libraries and create the helper functions for authenticating users and generating tokens.
Watch the following segments: Configuration (2:20 - 5:01): This covers installing dependencies and setting up the secret key, algorithm, and other initial configurations. authenticate_user function (12:00 - 13:20): This part details the logic for verifying user credentials against the database. create_access_token function (14:11 - 15:59): This explains how the JWT is encoded with a payload and expiration time.
Step 3: Defining Schemas and the OAuth2 Scheme
Before building the endpoint, we need two more things:
- A Pydantic schema for the token response.
- A way to tell FastAPI how to handle the login form and where to find tokens in future requests.
1. Token Schema
In your schemas.py file, add a new model for the token response:
# In schemas.py
from pydantic import BaseModel
# ... (your existing User schemas)
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
The Token schema defines the shape of the JSON we'll send back on a successful login. We'll use TokenData in the next lesson to represent the data we extract from a token.
2. OAuth2 Password Scheme
FastAPI has built-in support for OAuth2 security schemes. We'll use OAuth2PasswordBearer to signal to FastAPI and the interactive docs that our API uses bearer tokens for authentication.
In your auth_router.py file, add this line near the top:
# In auth_router.py
from fastapi.security import OAuth2PasswordBearer
# ... (other imports)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token")
The tokenUrl points to the login endpoint we are about to create. This is what enables the "Authorize" button in the Swagger UI.
Step 4: Implementing the Login Endpoint
Now we can assemble all the pieces into our login endpoint. We will use another FastAPI dependency, OAuth2PasswordRequestForm, which is designed specifically for this purpose. It parses the incoming form data and makes the username and password available.
In your auth_router.py, add the following endpoint:
# In auth_router.py
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from datetime import timedelta
# Import your helper functions and schemas
from . import crud, schemas, auth_utils
from .database import get_db
router = APIRouter(
prefix="/auth",
tags=["Authentication"]
)
# ... your existing /register endpoint
@router.post("/token", response_model=schemas.Token)
async def login_for_access_token(
form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db)
):
user = auth_utils.authenticate_user(
db,
username=form_data.username,
password=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=auth_utils.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = auth_utils.create_access_token(
data={"sub": user.username},
expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
Let's break down this endpoint:
@router.post("/token", ...): We define a POST endpoint at/auth/token.form_data: OAuth2PasswordRequestForm = Depends(): This is the magic. FastAPI injects a dependency that parses the request body as form data and provides an object withusernameandpasswordattributes.auth_utils.authenticate_user(...): We call our helper to validate the credentials.raise HTTPException(...): If authentication fails, we return a401 Unauthorizederror.auth_utils.create_access_token(...): If authentication succeeds, we create a token with the user's username as the subject (sub).return {"access_token": ..., "token_type": "bearer"}: We return the token in the format defined by ourTokenschema.

Step 5: Testing Your Login Endpoint
It's time to test our new endpoint. Run your application and go to the interactive docs (/docs).
- Locate the
POST /auth/tokenendpoint. - Click "Try it out".
- Enter the
usernameandpasswordfor the user you created in the previous lesson. - Click "Execute".
If the credentials are correct, you will receive a 200 OK response containing your access_token and token_type. Congratulations, your login system is working!
You can also now click the Authorize button at the top right of the docs. A dialog will pop up where you can paste the entire token (including the word "bearer", or simply the token string depending on the setup) to authenticate your session within the interactive docs. This will be essential for our next lesson.
For another excellent video guide on this process, including testing in the Swagger UI, please see the resource below.
Python FastAPI Tutorial (Part 10): Authentication - Registration and Login with JWT
Corey Schafer's FastAPI tutorial provides a very clear, hands-on implementation of a login endpoint and demonstrates how to test it effectively using the interactive docs.
Watch the following segments: Login Endpoint Implementation (26:37 - 33:05): This part walks through creating the login endpoint within a router, explaining the use of OAuth2PasswordRequestForm and the token generation process. Testing the Login (38:27 - 40:59): This segment shows how to use the interactive docs to log in, receive a token, and use the 'Authorize' button.
For reinforcement, you can also read through a concise text-based implementation.
Login & Registration System with JWT in FastAPI
The GeeksforGeeks article "Login & Registration System with JWT in FastAPI" offers a quick and clear summary of the entire login flow, from helper functions to the final endpoint.
Review 'Step 6: Helper functions (Security)' and the '/login' route within 'Step 8: Routes'. This will provide a solid text-based reference for the code we've written today.
Conclusion
Excellent work! You have now implemented a complete registration and login flow. Users can securely sign up, and they can log in to receive a JWT that proves their identity.
Key Takeaways:
- OAuth2 Forms: FastAPI's
OAuth2PasswordRequestFormsimplifies handling standard login requests. - JWT Creation: JWTs are created by encoding a payload (containing claims like
subandexp) with aSECRET_KEYand anALGORITHM. - Authentication Flow: The login endpoint orchestrates the process: it uses a helper function to authenticate credentials and another to create a token upon success.
- Security Best Practices: Returning a generic error on login failure prevents attackers from discovering valid usernames.
You now have the tools to issue tokens. In our next lesson, we'll learn how to consume them. We will build a dependency that can validate an incoming JWT and protect specific endpoints, ensuring only authenticated users can access them.