# How to Use OAuth2PasswordRequestForm for User Login in FastAPI

> Learn to use OAuth2PasswordRequestForm in FastAPI for secure user login. Implement OAuth2 password grants with JWT tokens and HttpOnly cookies for robust authentication.

- Repository: [Benav Labs/fastapi-boilerplate](https://github.com/benavlabs/fastapi-boilerplate)
- Tags: deep-dive
- Published: 2026-02-26

---

**The `OAuth2PasswordRequestForm` in the benavlabs/fastapi-boilerplate parses username and password from form-encoded requests, enabling standard OAuth2 password grant authentication with JWT access tokens and HttpOnly refresh cookies.**

The benavlabs/fastapi-boilerplate provides a production-ready authentication system built on FastAPI's OAuth2 utilities. This guide explains how the `OAuth2PasswordRequestForm` class handles user login requests, validates credentials against the database, and issues secure JWT tokens for session management.

## Understanding the OAuth2PasswordRequestForm Login Flow

The authentication flow relies on three core components working together to process login requests and manage token lifecycles.

### Core Components and File Locations

- **`OAuth2PasswordRequestForm`** – Parses form-encoded credentials in [`src/app/api/v1/login.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/api/v1/login.py)
- **`authenticate_user`** – Verifies bcrypt-hashed passwords in [`src/app/core/security.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/security.py)
- **`create_access_token` and `create_refresh_token`** – Generate signed JWTs in [`src/app/core/security.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/security.py)
- **`Token` schema** – Defines the response model in [`src/app/core/schemas.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/schemas.py)
- **[`crud_users.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/crud_users.py)** – Database queries for user lookup in [`src/app/crud/crud_users.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/crud/crud_users.py)

### Step-by-Step Authentication Process

1. The client sends a **POST request** to `/api/v1/login` with `Content-Type: application/x-www-form-urlencoded` containing `username` and `password` fields.
2. FastAPI injects an `OAuth2PasswordRequestForm` instance into the endpoint as `form_data`, automatically parsing the request body.
3. The endpoint calls `authenticate_user(username_or_email=form_data.username, password=form_data.password, db=db)` to validate credentials against the `users` table.
4. If authentication fails, an `UnauthorizedException` (HTTP 401) is raised immediately.
5. Upon successful validation, `create_access_token` and `create_refresh_token` generate signed JWTs using the secret key and algorithm defined in [`src/app/core/config.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/config.py).
6. The refresh token is stored in an **HttpOnly, Secure cookie** via `response.set_cookie`, preventing JavaScript access.
7. The endpoint returns a JSON body matching the `Token` schema, containing the `access_token` and `token_type: "bearer"`.

## Implementing the Login Endpoint with OAuth2PasswordRequestForm

The login route in [`src/app/api/v1/login.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/api/v1/login.py) uses FastAPI's dependency injection system to handle form parsing automatically.

```python
from fastapi import APIRouter, Depends, Response
from fastapi.security import OAuth2PasswordRequestForm
from typing import Annotated
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.db.database import async_get_db
from app.core.security import authenticate_user, create_access_token, create_refresh_token

router = APIRouter()

@router.post("/login")
async def login(
    response: Response,
    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
    db: Annotated[AsyncSession, Depends(async_get_db)]
):
    # Authenticate using the parsed form data

    user = await authenticate_user(
        username_or_email=form_data.username,
        password=form_data.password,
        db=db
    )
    
    # Generate tokens

    access_token = create_access_token(data={"sub": str(user.id)})
    refresh_token = create_refresh_token(data={"sub": str(user.id)})
    
    # Set HttpOnly cookie for refresh token

    response.set_cookie(
        key="refresh_token",
        value=refresh_token,
        httponly=True,
        secure=True,
        samesite="strict"
    )
    
    return {
        "access_token": access_token,
        "token_type": "bearer"
    }

```

The `Annotated[OAuth2PasswordRequestForm, Depends()]` declaration tells FastAPI to expect `application/x-www-form-urlencoded` data and automatically convert it into the form object.

## Authenticating Users and Generating Tokens

### Verifying Credentials with authenticate_user

The `authenticate_user` function in [`src/app/core/security.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/security.py) handles the actual credential verification using bcrypt.

```python
from passlib.context import CryptContext
from sqlalchemy.ext.asyncio import AsyncSession

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

async def authenticate_user(
    username_or_email: str,
    password: str,
    db: AsyncSession
):
    from app.crud.crud_users import crud_users
    
    # Query user by username or email

    user = await crud_users.get_by_username_or_email(db, username_or_email)
    
    if not user:
        return False
    
    # Verify password hash

    if not pwd_context.verify(password, user.hashed_password):
        return False
    
    return user

```

If the user does not exist or the password verification fails, the function returns `False`, triggering an HTTP 401 response in the login endpoint.

### Creating JWT Access and Refresh Tokens

Token generation uses the `create_access_token` and `create_refresh_token` functions from [`src/app/core/security.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/security.py), configured via [`src/app/core/config.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/config.py).

```python
from datetime import datetime, timedelta
from jose import jwt
from app.core.config import settings

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire, "type": "access"})
    return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)

def create_refresh_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
    to_encode.update({"exp": expire, "type": "refresh"})
    return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)

```

The `settings` object loads values from environment variables or `.env` files, including `SECRET_KEY`, `ALGORITHM` (typically HS256), and token expiry durations.

### Setting HttpOnly Cookies for Refresh Tokens

The login endpoint stores the refresh token in a secure cookie to prevent XSS attacks while maintaining session continuity.

```python
response.set_cookie(
    key="refresh_token",
    value=refresh_token,
    httponly=True,      # Prevents JavaScript access

    secure=True,        # Requires HTTPS in production

    samesite="strict",  # CSRF protection

    max_age=settings.REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60
)

```

This implementation follows the OAuth2 best practice of keeping long-lived refresh tokens in HttpOnly cookies while returning short-lived access tokens in the JSON response body.

## Refreshing Access Tokens

The boilerplate includes a dedicated refresh endpoint in [`src/app/api/v1/login.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/api/v1/login.py) that reads the refresh token from the cookie and issues a new access token.

```python
from app.core.security import verify_token
from app.core.schemas import Token

@router.post("/refresh", response_model=Token)
async def refresh_token(
    request: Request,
    db: Annotated[AsyncSession, Depends(async_get_db)]
):
    # Extract refresh token from HttpOnly cookie

    refresh_token = request.cookies.get("refresh_token")
    
    if not refresh_token:
        raise UnauthorizedException("Refresh token missing")
    
    # Verify token validity and type

    token_data = verify_token(refresh_token, token_type=TokenType.REFRESH, db=db)
    
    # Generate new access token

    new_access_token = create_access_token(data={"sub": token_data.sub})
    
    return {
        "access_token": new_access_token,
        "token_type": "bearer"
    }

```

The `verify_token` function checks the JWT signature, expiration, and token type against the database to ensure the token has not been revoked.

## Complete Code Examples

### Sending a Login Request with cURL

Test the OAuth2 password flow directly from the command line:

```bash
curl -X POST "http://localhost:8000/api/v1/login" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d "username=johndoe@example.com" \
     -d "password=secret123"

```

The response includes the access token in JSON and sets the `refresh_token` cookie automatically:

```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer"
}

```

### Automating Login with Python httpx

For integration tests or client applications, use `httpx` to handle the form encoding and cookie management:

```python
import httpx

def login_user(username: str, password: str) -> dict:
    """
    Authenticate using OAuth2PasswordRequestForm and return tokens.
    """
    url = "http://localhost:8000/api/v1/login"
    data = {"username": username, "password": password}
    
    with httpx.Client() as client:
        response = client.post(url, data=data)
        response.raise_for_status()
        
        # Extract JSON response (access token)

        token_data = response.json()
        access_token = token_data["access_token"]
        
        # Extract HttpOnly cookie (refresh token)

        refresh_token = response.cookies.get("refresh_token")
        
        return {
            "access_token": access_token,
            "refresh_token": refresh_token
        }

# Usage

tokens = login_user("johndoe@example.com", "secret123")
print(f"Access: {tokens['access_token'][:20]}...")

```

### Accessing Protected Endpoints

Use the access token as a Bearer token in the Authorization header:

```python
import httpx

def get_current_user(access_token: str):
    """
    Fetch current user data using the access token.
    """
    headers = {"Authorization": f"Bearer {access_token}"}
    url = "http://localhost:8000/api/v1/users/me"
    
    response = httpx.get(url, headers=headers)
    response.raise_for_status()
    return response.json()

# Usage

user_data = get_current_user(tokens["access_token"])
print(user_data["email"])

```

### Refreshing Expired Access Tokens

When the access token expires, use the refresh token from the cookie to obtain a new one:

```python
import httpx

def refresh_access_token(refresh_token: str) -> str:
    """
    Exchange refresh token for new access token.
    """
    url = "http://localhost:8000/api/v1/refresh"
    
    with httpx.Client() as client:
        # Set the refresh token in the cookie jar

        client.cookies.set("refresh_token", refresh_token)
        
        response = client.post(url)
        response.raise_for_status()
        
        return response.json()["access_token"]

# Usage when access token expires

new_token = refresh_access_token(tokens["refresh_token"])

```

## Summary

- **`OAuth2PasswordRequestForm`** automatically parses `username` and `password` from form-encoded POST requests in [`src/app/api/v1/login.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/api/v1/login.py).
- The **`authenticate_user`** function in [`src/app/core/security.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/security.py) validates credentials against bcrypt-hashed passwords stored via [`src/app/crud/crud_users.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/crud/crud_users.py).
- **Access tokens** are returned in the JSON response, while **refresh tokens** are stored in HttpOnly, Secure cookies to prevent XSS attacks.
- The **`/api/v1/refresh`** endpoint reads the cookie and issues new access tokens without requiring re-authentication.
- Configuration for token expiry and secrets resides in [`src/app/core/config.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/config.py), using environment variables for security.

## Frequently Asked Questions

### What is OAuth2PasswordRequestForm in FastAPI?

`OAuth2PasswordRequestForm` is a FastAPI dependency class that parses OAuth2 password grant requests. It expects `application/x-www-form-urlencoded` data containing `username` and `password` fields. In the benavlabs/fastapi-boilerplate, it is declared as `form_data: Annotated[OAuth2PasswordRequestForm, Depends()]` in the login endpoint, allowing automatic extraction of credentials without manual request body parsing.

### How does the refresh token flow work in this boilerplate?

The refresh flow uses a dual-token strategy. After initial login, the client receives a short-lived access token in the JSON response and a long-lived refresh token in an HttpOnly cookie. When the access token expires, the client calls `/api/v1/refresh`, which extracts the cookie, verifies it via `verify_token` in [`src/app/core/security.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/security.py), and returns a new access token. This pattern keeps refresh tokens out of JavaScript-accessible storage, mitigating XSS risks.

### Where are the JWT tokens configured in the boilerplate?

Token configuration resides in [`src/app/core/config.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/config.py), which loads environment variables for `SECRET_KEY`, `ALGORITHM` (typically HS256), `ACCESS_TOKEN_EXPIRE_MINUTES`, and `REFRESH_TOKEN_EXPIRE_DAYS`. The `create_access_token` and `create_refresh_token` functions in [`src/app/core/security.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/security.py) import these settings to set token expiration claims and signing parameters.

### How do I protect an endpoint using the access token?

Protected endpoints use the `get_current_user` dependency (typically defined in [`src/app/core/security.py`](https://github.com/benavlabs/fastapi-boilerplate/blob/main/src/app/core/security.py) or similar) which expects an `Authorization: Bearer <token>` header. The dependency extracts the token, validates it via `verify_token`, and returns the user object. To protect a route, inject this dependency into your endpoint function: `async def protected_route(current_user: Annotated[User, Depends(get_current_user)])`. If the token is missing or invalid, FastAPI returns a 401 Unauthorized response automatically.