Skip to main content

Advanced OAuth2: Scopes and Authorization Codes

When you build an application that integrates with third-party identity providers or requires granular user permissions, you need a way to manage complex authentication flows and verify specific access levels. FastAPI provides the OAuth2AuthorizationCodeBearer class to handle the metadata for the Authorization Code flow and the SecurityScopes class to manage fine-grained permissions (scopes) across your dependency tree.

Implementing the Authorization Code Flow

The Authorization Code flow is the standard for third-party integrations (like "Login with Google"). In this flow, the user is redirected to an external provider to authorize your app, which then receives a code to exchange for a token.

To define this scheme in FastAPI, instantiate OAuth2AuthorizationCodeBearer with the URLs provided by your identity provider:

from fastapi import FastAPI, Depends
from fastapi.security import OAuth2AuthorizationCodeBearer

app = FastAPI()

oauth2_scheme = OAuth2AuthorizationCodeBearer(
authorizationUrl="https://example.com/authorize",
tokenUrl="https://example.com/token",
scopes={
"items:read": "Read your items",
"items:write": "Modify your items"
}
)

@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}

How Token Extraction Works Internally

When a request hits a path operation using this dependency, the OAuth2AuthorizationCodeBearer.__call__ method (located in fastapi/security/oauth2.py) executes:

  1. It retrieves the Authorization header from the request.
  2. It uses get_authorization_scheme_param to split the header into a scheme and a parameter.
  3. If the header is missing or the scheme is not "bearer", it raises an HTTPException with a 401 status code and a WWW-Authenticate: Bearer header (via self.make_not_authenticated_error()).
  4. If successful, it returns the token string (the param).

If you set auto_error=False in the constructor, the dependency returns None instead of raising an exception when the header is missing, allowing for optional authentication.

Managing Permissions with Scopes

Scopes allow you to define specific permissions that a token must have to access certain resources. While OAuth2AuthorizationCodeBearer defines the available scopes for OpenAPI documentation, you use the Security function to require them on specific routes.

Requiring Scopes in Path Operations

Instead of Depends, use Security to specify the required scopes for a route:

from fastapi import Security

@app.get("/items/update", dependencies=[Security(oauth2_scheme, scopes=["items:write"])])
async def update_item():
return {"message": "Item updated"}

Dynamic Verification with SecurityScopes

To verify these scopes within your own dependency logic (e.g., checking them against the scopes encoded in a JWT), inject the SecurityScopes class. FastAPI automatically populates this class with the scopes required by the current path operation and all parent dependencies in the chain.

from fastapi import HTTPException, Security
from fastapi.security import SecurityScopes

async def get_current_user(
security_scopes: SecurityScopes,
token: str = Depends(oauth2_scheme)
):
if security_scopes.scopes:
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
else:
authenticate_value = "Bearer"

# Logic to decode token and extract user scopes
user_scopes = ["items:read"] # Example extracted from token

for scope in security_scopes.scopes:
if scope not in user_scopes:
raise HTTPException(
status_code=401,
detail="Not enough permissions",
headers={"WWW-Authenticate": authenticate_value},
)
return {"user": "admin", "scopes": user_scopes}

@app.get("/users/me")
async def read_users_me(
current_user: dict = Security(get_current_user, scopes=["items:read"])
):
return current_user

The SecurityScopes Implementation

The SecurityScopes class in fastapi/security/oauth2.py is designed to simplify scope handling:

  • self.scopes: A list of all unique scope strings required by the dependency and any parent dependencies.
  • self.scope_str: A single string where scopes are joined by spaces (e.g., "items:read items:write"), which is the format required by the OAuth2 specification for the WWW-Authenticate header.

By using SecurityScopes, you can write a single reusable authentication dependency that adapts its validation logic based on the specific requirements of the route it is protecting.