tiangolo/fastapi · error · HTTPException

Incorrect username or password

Error message

Incorrect username or password

What it means

Legacy-DI variant of error 41. The /token handler raises HTTP 400 'Incorrect username or password' when fake_users_db.get(form_data.username) is None. Same anti-enumeration rationale and same message as the password-mismatch branch at line 81: an attacker cannot tell whether the username or the password was wrong.

Solutions

  1. POST username exactly matching a fake_users_db key plus the matching password.
  2. Verify the OAuth2PasswordRequestForm field name is 'username' and Content-Type is application/x-www-form-urlencoded.
  3. Normalize input (strip/lower) before lookup if your domain requires case-insensitive logins.
Defensive patterns

Strategy: validation

Validate before calling

KNOWN_USERS = {"johndoe", "alice"}
def valid_login_form(username: str, password: str) -> bool:
    return isinstance(username, str) and username.strip() in KNOWN_USERS and bool(password)

Type guard

from typing import TypeGuard
def is_non_empty_creds(pair: tuple) -> TypeGuard[tuple[str, str]]:
    u, p = pair
    return isinstance(u, str) and isinstance(p, str) and u.strip() != "" and p != ""

Try / catch

import httpx
try:
    r = httpx.post("/token", data={"username": u, "password": p})
except httpx.HTTPStatusError as e:
    if e.response.status_code in (400, 401):
        show_generic_login_error()

Prevention

When it happens

Trigger: POST /token with username not equal to 'johndoe' or 'alice' (case/whitespace sensitive), or with the field misnamed (e.g. 'user' instead of 'username').

Common situations: Case mismatch, copy-paste whitespace, JSON body instead of form-encoded, stale test fixture.

Related errors


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

Appendix: source

Thrown at docs_src/security/tutorial003_py310.py:77

        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Not authenticated",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return user


async def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@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=400, detail="Incorrect username or password")
    user = UserInDB(**user_dict)
    hashed_password = fake_hash_password(form_data.password)
    if not hashed_password == user.hashed_password:
        raise HTTPException(status_code=400, detail="Incorrect username or password")

    return {"access_token": user.username, "token_type": "bearer"}


@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user

View on GitHub (pinned to 3e8d1526d8)