tiangolo/fastapi · error · HTTPException

Incorrect username or password

Error message

Incorrect username or password

What it means

Raised by the `/token` login handler with HTTP 400 when `fake_users_db.get(form_data.username)` returns nothing - i.e. the submitted username does not exist. The message deliberately does not distinguish 'no such user' from 'wrong password' to avoid user enumeration.

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 42a41db11f)

Solutions

  1. Send credentials as `application/x-www-form-urlencoded` (OAuth2PasswordRequestForm), not JSON.
  2. Confirm the username exists in the user store (e.g. `johndoe`).
  3. Check the password too - the same message is reused for wrong password (line 85).

Example fix

# before
curl -X POST http://localhost:8000/token -H 'Content-Type: application/json' \
  -d '{"username":"johndoe","password":"secret"}'
# after
curl -X POST http://localhost:8000/token \
  -d 'username=johndoe&password=secret'
Defensive patterns

Strategy: try-catch

Validate before calling

def is_form_login(username: str, password: str) -> bool:
    return bool(username) and bool(password)
# POST with Content-Type: application/x-www-form-urlencoded when True

Type guard

def is_credentials_error(resp) -> bool:
    return getattr(resp, 'status_code', None) in (400, 401) and \
           resp.json().get('detail') == 'Incorrect username or password'

Try / catch

try:
    tok = client.post('/token', data={'username': u, 'password': p})
    tok.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.json().get('detail') == 'Incorrect username or password':
        show_user_friendly_login_error()  # do NOT reveal which part was wrong
    raise

Prevention

When it happens

Trigger: `POST /token` (form-encoded) with `username=nobody` where `nobody` is not a key in `fake_users_db`. The check is `if not user_dict:` at line 80.

Common situations: Typo in the username; client posting JSON instead of OAuth2 form data so `form_data.username` is empty; user not yet provisioned; environment DB missing the seed users.

Related errors


AI-assisted analysis of tiangolo/fastapi@42a41db11f (2026-08-04). Data as JSON: /data/errors/b7bb8d01417dc191.json. Report an issue: GitHub.