Skip to main content

Tutorial: Simple OAuth2 with Password and Bearer

By the end of this tutorial, you will have a functional authentication system that allows users to log in with a username and password to receive a Bearer token, which they can then use to access protected routes.

Prerequisites

To follow this tutorial, you need fastapi and pydantic installed. You should also be familiar with FastAPI dependencies.

Step 1: Define the Security Scheme

First, you need to create an instance of OAuth2PasswordBearer. This class tells FastAPI that the application uses the OAuth2 "password flow" and where the client should send the credentials to get a token.

from fastapi import FastAPI
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()

# The tokenUrl points to the relative path where we will define our login logic
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

The OAuth2PasswordBearer class is a dependency that looks for an Authorization header in the request. It specifically checks for a value starting with Bearer . If the header is missing or incorrect, it automatically raises a 401 Unauthorized error (unless you set auto_error=False). The tokenUrl="token" parameter is used by OpenAPI (Swagger UI) to provide a "Authorize" button that sends requests to the /token endpoint.

Step 2: Create the Login Path Operation

Next, you will create the /token endpoint. This endpoint must receive the credentials as form data, as required by the OAuth2 specification. You use OAuth2PasswordRequestForm to handle this.

from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm

# Mock database for demonstration
fake_users_db = {
"johndoe": {"username": "johndoe", "password": "secretpassword"}
}

@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
user_dict = fake_users_db.get(form_data.username)
if not user_dict or form_data.password != user_dict["password"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Incorrect username or password"
)

# In a real app, you would return a real JWT here
return {"access_token": form_data.username, "token_type": "bearer"}

The OAuth2PasswordRequestForm dependency extracts several fields from the request body:

  • username: The user's identifier.
  • password: The user's password.
  • scope: A string of space-separated scopes (accessible as a list via form_data.scopes).
  • grant_type: Optional field (defaults to None in this class).

Note that this class expects application/x-www-form-urlencoded data, not JSON.

Step 3: Create a Dependency to Get the Current User

Now that you can generate a token (in this case, just the username), you need a way to validate that token in other routes. You do this by creating a dependency that depends on the oauth2_scheme you defined in Step 1.

async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
# The 'token' variable here is the string extracted from the Bearer header
user = fake_users_db.get(token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return user

When a route uses get_current_user as a dependency, FastAPI will first call oauth2_scheme. If a valid Bearer token is found, it is passed to get_current_user as the token argument.

Step 4: Protect an Endpoint

Finally, use your get_current_user dependency to protect a path operation.

@app.get("/users/me")
async def read_users_me(current_user: Annotated[dict, Depends(get_current_user)]):
return current_user

When you visit /docs, you will see an "Authorize" button. You can enter "johndoe" and "secretpassword", and Swagger UI will automatically handle the form submission to /token and include the resulting token in the Authorization header for all subsequent requests to /users/me.

Using Strict Validation

If your application must strictly adhere to the OAuth2 specification, you can use OAuth2PasswordRequestFormStrict instead of OAuth2PasswordRequestForm.

from fastapi.security import OAuth2PasswordRequestFormStrict

@app.post("/token-strict")
async def login_strict(form_data: Annotated[OAuth2PasswordRequestFormStrict, Depends()]):
# This will fail if the client does not send grant_type="password"
return {"access_token": form_data.username, "token_type": "bearer"}

The only difference is that OAuth2PasswordRequestFormStrict enforces that the grant_type field is present and set exactly to "password", whereas the standard OAuth2PasswordRequestForm makes it optional for better compatibility with simpler clients.

Complete Result

Your final implementation combines these components to create a secure flow:

  1. Client sends credentials to /token via form data.
  2. OAuth2PasswordRequestForm parses the credentials.
  3. Server validates credentials and returns a token.
  4. Client sends the token in the Authorization: Bearer <token> header to /users/me.
  5. OAuth2PasswordBearer extracts the token.
  6. get_current_user validates the token and returns the user object.
  7. Path Operation receives the user object and returns the response.