Hello! Welcome back to our module on Authentication and Authorization in FastAPI.
In the last lesson, we built the essential utility functions for security: one for hashing and verifying passwords (pwdlib) and others for creating and decoding JWTs (PyJWT). We now have the building blocks, but they aren't connected to our API. A user has no way to actually request a token.
Today, we'll bridge that gap. Our goal is to build a login endpoint that returns a JWT token upon valid credentials using an OAuth2PasswordRequestForm dependency. This endpoint will be the public gateway for users to authenticate themselves.
1. The "Password Flow" and FastAPI's Helper
In your work with Django REST Framework, you've likely used an endpoint from a library like django-rest-framework-simplejwt (e.g., TokenObtainPairView). You send a username and password, and you get back an access and refresh token. This standardized process is part of the OAuth2 specification and is known as the "Resource Owner Password Credentials Grant" flow, or more simply, the "password flow".
The key detail is that this flow traditionally expects the credentials to be sent not as JSON, but as form data (application/x-www-form-urlencoded).
FastAPI provides a dependency to handle this specific format for you: OAuth2PasswordRequestForm. When you use it in your path operation, FastAPI knows to:
- Expect form data with
usernameandpasswordfields. - Parse this data and make it available to your function.
- Automatically update your OpenAPI documentation to show the correct input fields.
This is what powers the "Authorize" button you often see in Swagger UI. By creating an endpoint that provides a token, you allow yourself (and other developers) to authenticate directly within the interactive docs to test protected endpoints.

2. Building the Login Endpoint
Let's construct the endpoint. The process involves three main steps:
- Accept the user's credentials.
- Authenticate them against our database.
- If successful, generate and return a JWT.
The official FastAPI documentation provides a superb, complete example of this. I encourage you to read through it as our primary reference for this section.
OAuth2 with Password (and hashing), Bearer with JWT tokens
This is the official FastAPI documentation on OAuth2 with JWT. It covers the entire flow, from helper functions to the final endpoint.
Please read the section titled 'Update the /token path operation'. It contains the complete code for the login endpoint. Pay close attention to how login_for_access_token uses OAuth2PasswordRequestForm = Depends() (or the Annotated version) to get the form data.
Now, let's assemble the code for our endpoint, which we can place in an auth router file. This code assumes you have the authenticate_user and create_access_token functions from our previous lessons.
from datetime import timedelta
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
# Assuming these are in your project structure
from . import utils # Where create_access_token lives
from . import schemas # Where your Pydantic models live
from . import crud # Where authenticate_user would be if using a DB
router = APIRouter(
prefix="/auth",
tags=["Authentication"]
)
@router.post("/token", response_model=schemas.Token)
async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
):
# 1. Authenticate the user
# This function should find the user and verify the password.
# It returns the user object on success, or False on failure.
user = crud.authenticate_user(
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"},
)
# 2. Create the token
access_token_expires = timedelta(minutes=utils.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = utils.create_access_token(
data={"sub": user.username}, # 'sub' is the standard claim for subject (user identifier)
expires_delta=access_token_expires
)
# 3. Return the token
return {"access_token": access_token, "token_type": "bearer"}
Let's break down this function:
@router.post("/token", response_model=schemas.Token): We define aPOSTendpoint at/token. Theresponse_modelensures our output matches theTokenPydantic schema (which should containaccess_tokenandtoken_typefields).form_data: Annotated[OAuth2PasswordRequestForm, Depends()]: This is the core of the magic. We use dependency injection to get an instance ofOAuth2PasswordRequestForm. Theform_dataobject will have.usernameand.passwordattributes containing the user's input.user = crud.authenticate_user(...): We call our authentication logic. This function is responsible for finding the user and checking if the provided password hash matches the one in the database.if not user: raise HTTPException(...): If authentication fails, we immediately stop and return a401 Unauthorizederror. It's crucial to provide a generic error message to prevent attackers from discovering valid usernames (a "user enumeration" attack). TheWWW-Authenticate: Bearerheader is part of the OAuth2 standard.access_token = utils.create_access_token(...): If authentication succeeds, we call the token creation utility from our last lesson. We pass the user's unique identifier (here, the username) as thesub(subject) claim.return {"access_token": access_token, "token_type": "bearer"}: Finally, we return the newly created token. Thetoken_type"bearer" tells the client how to use it (i.e., in anAuthorization: Bearer <token>header).
3. A Visual Walkthrough
Reading code is one thing; seeing it implemented and run is another. The following video provides a clear, step-by-step demonstration of building this exact endpoint.
Fast API Tutorial, Part 27: Security with JWT
This video from JVP Design walks through creating the /token route. Watching someone else code it can help solidify the concepts and flow.
Watch the segment from 00:12:27 to 00:16:24. The presenter implements the login_for_access_token function, explaining each part as they go, from injecting the OAuth2PasswordRequestForm to raising the HTTPException on failure.
This video nicely ties together the authentication helpers (like authenticate_user) with the endpoint logic, showing how they cooperate to secure your application.
4. Integrating into a Larger Project
As you are interested in modern project structures, it's worth seeing how this login route fits into a well-organized application. The code snippet I provided earlier used an APIRouter. This is the standard FastAPI way to organize your endpoints, analogous to including app-specific urls.py files in Django.
The next video shows this login endpoint being created within a dedicated auth router as part of a larger, structured project.
FastAPI JWT Authentication 2025 (JSON Web Tokens)
This video from Tenacity demonstrates building the login endpoint within a more structured project, using an APIRouter to organize the authentication-related routes.
Watch from 00:13:03 to 00:15:12. Focus on how the @router.post('/token') is defined within a dedicated authentication router file. This aligns with your goal of learning professional code structuring.
Conclusion
Excellent work! You have now built the front door to your API's authenticated features. By creating a login endpoint, you've connected your user data, password verification logic, and JWT generation into a cohesive authentication flow.
Key Takeaways:
- The OAuth2 "password flow" is a standard for exchanging a username and password for an access token.
- FastAPI's
OAuth2PasswordRequestFormis a dependency that handles parsingx-www-form-urlencodedcredentials. - The login endpoint acts as an orchestrator: it uses the
authenticate_userhelper to verify credentials and thecreate_access_tokenhelper to generate a JWT. - Failed login attempts should raise a
401 UnauthorizedHTTPExceptionwith a generic error message to enhance security. - The generated JWT is returned to the client, typically with a
token_typeof"bearer".
Preview of the Next Lesson:
A user can now log in and receive a token, but this token doesn't do anything yet. Our other endpoints are still wide open. In the next lesson, we will solve this by implementing a dependency that extracts and validates a JWT from request headers using OAuth2PasswordBearer. This will allow us to protect routes and identify the currently authenticated user for any given request.