tiangolo/fastapi · error · HTTPException

Incorrect username or password

Error message

Incorrect username or password

What it means

Raised in the /token handler when fake_users_db.get(form_data.username) returns None, i.e. the submitted username is not a known account. The message is intentionally identical to the password-mismatch branch at line 85 so that an attacker cannot distinguish 'user does not exist' from 'wrong password' (anti user-enumeration). The tutorial uses HTTP 400; the OAuth2/RFC 6749 convention is 401 with WWW-Authenticate.

Solutions

  1. POST with username exactly matching a fake_users_db key (johndoe or alice) and the matching password.
  2. Confirm the form field is named 'username' and the Content-Type is application/x-www-form-urlencoded.
  3. If case-insensitive logins are desired, normalize input with username.strip().lower() before the lookup.

Example fix

// before
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
    raise HTTPException(status_code=400, detail="Incorrect username or password")

// after
user_dict = fake_users_db.get(form_data.username.strip())
if not user_dict:
    raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"})
Defensive patterns

Strategy: validation

Validate before calling

# Validate before POST /token
KNOWN_USERS = {"johndoe", "alice"}
def valid_login_form(username: str, password: str) -> bool:
    return (
        isinstance(username, str) and isinstance(password, str)
        and username.strip() in KNOWN_USERS
        and len(password) > 0
    )

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):
        # treat both branches identically (server hides which failed)
        show_generic_login_error()

Prevention

When it happens

Trigger: POST /token with an OAuth2PasswordRequestForm whose username field is not exactly 'johndoe' or 'alice' (case/whitespace sensitive): typos, 'JohnDoe', a leading space, or the field named 'user'/'email' instead of 'username'.

Common situations: Case mismatch on a case-sensitive store; copy-paste whitespace; client sending JSON body instead of application/x-www-form-urlencoded; integration tests using a stale fixture username after the db dict was edited.

Related errors


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

Appendix: source

Thrown at docs_src/security/tutorial003_an_py310.py:81

            detail="Not authenticated",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return user


async def get_current_active_user(
    current_user: Annotated[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: Annotated[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: Annotated[User, Depends(get_current_active_user)],
):
    return current_user

View on GitHub (pinned to 3e8d1526d8)