Let’s start building a real authentication system using OAuth2 with Password flow. This is the most common authentication pattern for modern APIs.
OAuth2 Password Bearer
First, we’ll use OAuth2PasswordBearer to tell FastAPI how authentication works in your API.
Here’s the simplest possible OAuth2 implementation:
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer( tokenUrl = "token" )
@app.get ( "/items/" )
async def read_items ( token : str = Depends(oauth2_scheme)):
return { "token" : token}
Create the OAuth2 scheme
OAuth2PasswordBearer(tokenUrl="token") tells FastAPI:
Tokens are obtained from the /token endpoint
The client should send tokens in the Authorization: Bearer <token> header
Use as a dependency
token: str = Depends(oauth2_scheme) tells FastAPI to:
Check for the Authorization header
Verify it starts with Bearer
Extract the token part
Pass it to your function
Automatic OpenAPI integration
Visit /docs and you’ll see an “Authorize” button. FastAPI automatically added it based on your security scheme!
This code only extracts the token—it doesn’t validate it! Anyone can send any string and it will be accepted. We’ll fix this in the next sections.
How OAuth2PasswordBearer Works
When you call the /items/ endpoint:
curl -X GET "http://localhost:8000/items/" \
-H "Authorization: Bearer my_secret_token"
FastAPI will:
Check the Authorization header exists
Verify it starts with Bearer
Extract my_secret_token
Pass it to your function as the token parameter
If the Authorization header is missing or invalid, FastAPI automatically returns:
{
"detail" : "Not authenticated"
}
With status code 401 Unauthorized and header WWW-Authenticate: Bearer.
Creating the Login Endpoint
Now let’s create the /token endpoint that issues tokens. We’ll use OAuth2PasswordRequestForm to collect the username and password:
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer( tokenUrl = "token" )
fake_users_db = {
"johndoe" : {
"username" : "johndoe" ,
"full_name" : "John Doe" ,
"email" : "[email protected] " ,
"hashed_password" : "fakehashedsecret" ,
"disabled" : False ,
}
}
def fake_hash_password ( password : str ):
return "fakehashed" + password
@app.post ( "/token" )
async def login ( form_data : OAuth2PasswordRequestForm = Depends()):
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
raise HTTPException(
status_code = status. HTTP_401_UNAUTHORIZED ,
detail = "Incorrect username or password" ,
headers = { "WWW-Authenticate" : "Bearer" },
)
hashed_password = fake_hash_password(form_data.password)
if hashed_password != user_dict[ "hashed_password" ]:
raise HTTPException(
status_code = status. HTTP_401_UNAUTHORIZED ,
detail = "Incorrect username or password" ,
headers = { "WWW-Authenticate" : "Bearer" },
)
# In this simple example, we return the username as the token
return { "access_token" : user_dict[ "username" ], "token_type" : "bearer" }
@app.get ( "/items/" )
async def read_items ( token : str = Depends(oauth2_scheme)):
return { "token" : token}
The OAuth2PasswordRequestForm is a dependency class that extracts form data from the request:
The username field (required by OAuth2 spec)
The password field (required by OAuth2 spec)
Optional scopes separated by spaces (e.g., “items:read items:write”)
Should be “password” according to OAuth2 spec (but this form is lenient)
Testing the Login
You can test the login endpoint with cURL:
curl -X POST "http://localhost:8000/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=johndoe&password=secret"
Response:
{
"access_token" : "johndoe" ,
"token_type" : "bearer"
}
The OAuth2 specification requires using form data (not JSON) for the token endpoint. FastAPI handles this automatically with OAuth2PasswordRequestForm.
Testing in the Docs
Now visit /docs and try the authentication flow:
Click Authorize
Click the “Authorize” button at the top right
Enter credentials
Enter username johndoe and password secret
Authorize
Click “Authorize” to save the credentials
Test protected endpoints
Now try calling /items/ - it will automatically include your token!
What We’ve Built (and What’s Missing)
What Works
✅ OAuth2 password flow
✅ Token endpoint that validates credentials
✅ Protected endpoints that require authentication
✅ Automatic OpenAPI documentation with “Authorize” button
What’s Still Missing
No real password hashing : We’re using "fakehashed" + password
No real tokens : We’re returning the username as the token
No token validation : Any string is accepted as a valid token
No token expiration : Tokens never expire
In the next sections, we’ll add:
Proper user retrieval and validation
Real JWT tokens with expiration
Password hashing with industry-standard algorithms
According to OAuth2 spec, the token endpoint must return JSON with:
{
"access_token" : "<the-token>" ,
"token_type" : "bearer"
}
The token_type must be "bearer" (lowercase) for OAuth2 password flow.
You can also return optional fields like expires_in (seconds until expiration), refresh_token, and scope.
Next Steps
Now that you understand the basic OAuth2 flow, let’s build a proper dependency to get the current authenticated user:
Get Current User Learn how to create a reusable dependency that validates tokens and returns user information