OAuth2 Password Flow with Bearer Tokens
In this tutorial, you will build a secure authentication system using the OAuth2 password flow. You will create a login endpoint that accepts credentials and returns a bearer token, and then use that token to protect other parts of your API.
By the end of this guide, you will have a working FastAPI application that uses OAuth2PasswordBearer to secure routes and OAuth2PasswordRequestForm to handle user logins.
Prerequisites
To follow this tutorial, you need FastAPI installed. You should also be familiar with how FastAPI dependencies work.
Step 1: Define the Security Scheme
The first step is to define how the client should provide the authentication token. In the OAuth2 password flow, the client sends a username and password to a specific URL and receives a token in return.
FastAPI provides the OAuth2PasswordBearer class to handle this.
from fastapi import FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
When you create an instance of OAuth2PasswordBearer, you pass a tokenUrl. This is the relative URL that the client (like the Swagger UI) will use to send the username and password to get a token.
The oauth2_scheme object is a dependency. When you use it in a path operation, FastAPI will look for an Authorization header in the request. It expects the header to look like Authorization: Bearer <token>. If the header is missing or doesn't use the Bearer scheme, FastAPI will automatically return a 401 Unauthorized error.
Step 2: Create the Token Endpoint
Now you need to create the path operation that actually handles the login. This endpoint must be a POST request to the same path you defined in tokenUrl.
To parse the login credentials, use the OAuth2PasswordRequestForm dependency.
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
# ... (app and oauth2_scheme defined above)
@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
# In a real app, you would verify the password against a database
if form_data.username != "johndoe" or form_data.password != "secret":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
)
return {"access_token": form_data.username, "token_type": "bearer"}
The OAuth2PasswordRequestForm class is specifically designed to parse form data. According to the OAuth2 specification, the login request must be sent as form data (application/x-www-form-urlencoded), not as JSON.
This dependency provides several attributes:
form_data.username: The username string.form_data.password: The password string.form_data.scopes: A list of strings parsed from thescopeform field.form_data.client_idandform_data.client_secret: Optional fields for client authentication.
Step 3: Secure Path Operations
To protect an endpoint, use the oauth2_scheme as a dependency. This will extract the token from the request and make it available to your function.
@app.get("/users/me")
async def read_users_me(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
When a client makes a request to /users/me, FastAPI will:
- Check for the
Authorizationheader. - Verify it uses the
Bearerscheme. - Extract the token string.
- Pass that string as the
tokenargument to your function.
If the token is missing, the user will receive a 401 Unauthorized response with a WWW-Authenticate: Bearer header, as implemented in the OAuth2.make_not_authenticated_error method.
Step 4: Enforce Strict Specification Compliance
The OAuth2 specification states that the grant_type field in the login form is required and must be the string "password". By default, OAuth2PasswordRequestForm is permissive and makes this field optional.
If you want to strictly enforce the specification, use OAuth2PasswordRequestFormStrict instead.
from fastapi.security import OAuth2PasswordRequestFormStrict
@app.post("/token-strict")
async def login_strict(form_data: Annotated[OAuth2PasswordRequestFormStrict, Depends()]):
return {"access_token": form_data.username, "token_type": "bearer"}
OAuth2PasswordRequestFormStrict uses a regex pattern ^password$ on the grant_type field to ensure it matches exactly.
Step 5: Optional Authentication
In some cases, you might want an endpoint to be accessible to both anonymous and authenticated users. You can achieve this by setting auto_error=False when instantiating OAuth2PasswordBearer.
optional_oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
@app.get("/items")
async def read_items(token: Annotated[str | None, Depends(optional_oauth2_scheme)]):
if token:
return {"message": f"Hello authenticated user with token {token}"}
return {"message": "Hello anonymous user"}
When auto_error is False, FastAPI will return None instead of raising an HTTPException if the Authorization header is missing or invalid. This allows your path operation logic to decide how to handle the unauthenticated state.
Complete Working Result
Here is the complete code combining these steps into a functional API:
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
# Simple mock validation
if form_data.username == "johndoe" and form_data.password == "secret":
return {"access_token": "fake-token-for-johndoe", "token_type": "bearer"}
raise HTTPException(status_code=400, detail="Incorrect username or password")
@app.get("/users/me")
async def read_users_me(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
To test this, run your application and navigate to /docs. You will see an Authorize button. Because you provided a tokenUrl, Swagger UI knows how to send the form data to your /token endpoint and store the resulting token to use in subsequent requests to /users/me.