Skip to main content

HTTP Basic and Digest Auth

FastAPI provides built-in support for standard HTTP authentication schemes, including HTTP Basic and a stub for HTTP Digest. These tools allow you to secure your API endpoints by requiring clients to provide credentials in the Authorization header.

Setting Up HTTP Basic Authentication

HTTP Basic authentication is a simple method where the client sends a username and password encoded in Base64. In FastAPI, you use the HTTPBasic class to define this security scheme and HTTPBasicCredentials to handle the resulting data.

To get started, create an instance of HTTPBasic and use it as a dependency in your path operation.

from typing import Annotated

from fastapi import Depends, FastAPI
from fastapi.security import HTTPBasic, HTTPBasicCredentials

app = FastAPI()

security = HTTPBasic()


@app.get("/users/me")
def read_current_user(
credentials: Annotated[HTTPBasicCredentials, Depends(security)]
):
return {"username": credentials.username, "password": credentials.password}

When you access this endpoint, FastAPI will look for the Authorization header. If it's missing or doesn't use the Basic scheme, FastAPI will automatically return a 401 Unauthorized response because auto_error is True by default. If the header is present, FastAPI decodes the Base64 string and populates an HTTPBasicCredentials object with the username and password.

Verifying Credentials Securely

In a real application, you need to verify the provided credentials against your database or configuration. To protect against timing attacks, FastAPI recommends using secrets.compare_digest.

The following example demonstrates how to create a dependency that validates the username and password:

import secrets

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials

app = FastAPI()

security = HTTPBasic()


def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = b"stanleyjobson"
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = b"swordfish"
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)
if not (is_correct_username and is_correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username


@app.get("/users/me")
def read_current_user(username: str = Depends(get_current_username)):
return {"username": username}

By using secrets.compare_digest, you ensure that the comparison time is constant regardless of how many characters match, preventing attackers from guessing credentials based on response times.

Customizing the Authentication Realm

The realm parameter in HTTPBasic allows you to specify a "realm" for the authentication. Browsers often use this string to identify which set of credentials to use or to display a specific message in the login prompt.

security = HTTPBasic(realm="API Access")

@app.get("/secure-data")
def get_secure_data(credentials: HTTPBasicCredentials = Depends(security)):
return {"message": "Access granted"}

When a request fails authentication, FastAPI includes this realm in the WWW-Authenticate header: WWW-Authenticate: Basic realm="API Access".

Optional Authentication

If you want an endpoint to be accessible to both anonymous and authenticated users, set auto_error=False. When this is set, the dependency will return None instead of raising an exception if the Authorization header is missing.

security = HTTPBasic(auto_error=False)

@app.get("/profile")
def read_profile(credentials: HTTPBasicCredentials | None = Depends(security)):
if credentials:
return {"user": credentials.username, "status": "authenticated"}
return {"user": "guest", "status": "anonymous"}

Note that if the header is present but malformed (e.g., invalid Base64), HTTPBasic will still raise an HTTPException even if auto_error is False.

Using the HTTP Digest Stub

FastAPI includes an HTTPDigest class, but it is currently a stub. It handles the extraction of the Authorization header and integrates with the OpenAPI (Swagger) UI, but it does not implement the complex challenge-response logic (nonces, opaque values, etc.) required by RFC 7616.

If you use HTTPDigest, the dependency result is an HTTPAuthorizationCredentials object containing the raw credentials string.

from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest

security = HTTPDigest()

@app.get("/digest-data")
def read_digest_data(
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)]
):
# credentials.credentials contains the raw digest string
return {"scheme": credentials.scheme, "raw_data": credentials.credentials}

To implement full Digest authentication, you should subclass HTTPDigest and override the __call__ method to perform the necessary cryptographic verification.

Security Considerations

  • HTTPS: HTTP Basic authentication sends credentials in a format that is easily reversible (Base64). You must use HTTPS in production to ensure that the Authorization header is encrypted during transit.
  • Encoding: HTTPBasic in FastAPI decodes the credentials using ascii by default. If your users might use non-ASCII characters in passwords, ensure you handle the resulting HTTPException or pre-process the data as needed.