tiangolo/fastapi · error · HTTPException

Incorrect username or password

Error message

Incorrect username or password

What it means

Legacy-DI variant of error 57. get_current_username compares username/password with secrets.compare_digest and raises HTTP 401 'Incorrect username or password' with WWW-Authenticate: Basic when either fails. The combined AND prevents timing short-circuit. Semantics identical to the Annotated version; only the DI syntax differs.

Source

Thrown at docs_src/security/tutorial007_py310.py:23

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}

View on GitHub (pinned to 3e8d1526d8)

Solutions

  1. Send Authorization: Basic <base64('stanleyjobson:swordfish')>.
  2. Clear cached Basic credentials in the browser and re-authenticate.
  3. Externalize credentials to env/secrets instead of hardcoding.
Defensive patterns

Strategy: validation

Validate before calling

import base64, secrets
USER, PW = b"stanleyjobson", b"swordfish"
def basic_header(username: str, password: str) -> str | None:
    if not (secrets.compare_digest(username.encode(), USER)
            and secrets.compare_digest(password.encode(), PW)):
        return None
    token = base64.b64encode(f"{username}:{password}".encode()).decode()
    return f"Basic {token}"

Type guard

from typing import TypeGuard
def is_valid_basic_pair(pair: tuple) -> TypeGuard[tuple[str, str]]:
    u, p = pair
    return isinstance(u, str) and isinstance(p, str) and u and p

Try / catch

import httpx
try:
    r = httpx.get("/users/me", headers={"Authorization": basic_header(u, p)})
    r.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 401:
        prompt_credentials()

Prevention

When it happens

Trigger: GET /users/me with Basic credentials whose username is not 'stanleyjobson' or whose password is not 'swordfish' (or with no Authorization header).

Common situations: Credential typo; cached browser creds; mis-encoded test header; rotated demo creds.

Related errors


AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11). Data as JSON: /api/errors/42001f4e533714a0. Report an issue: GitHub.