{"record":{"id":"8b5d2ffd48e1d414","repo":"tiangolo/fastapi","slug":"incorrect-username-or-password-8b5d2f","errorCode":null,"errorMessage":"Incorrect username or password","messagePattern":"Incorrect username or password","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"docs_src/security/tutorial005_an_py310.py","lineNumber":157,"sourceCode":"            )\n    return user\n\n\nasync def get_current_active_user(\n    current_user: Annotated[User, Security(get_current_user, scopes=[\"me\"])],\n):\n    if current_user.disabled:\n        raise HTTPException(status_code=400, detail=\"Inactive user\")\n    return current_user\n\n\n@app.post(\"/token\")\nasync def login_for_access_token(\n    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],\n) -> Token:\n    user = authenticate_user(fake_users_db, form_data.username, form_data.password)\n    if not user:\n        raise HTTPException(status_code=400, detail=\"Incorrect username or password\")\n    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)\n    access_token = create_access_token(\n        data={\"sub\": user.username, \"scope\": \" \".join(form_data.scopes)},\n        expires_delta=access_token_expires,\n    )\n    return Token(access_token=access_token, token_type=\"bearer\")\n\n\n@app.get(\"/users/me/\")\nasync def read_users_me(\n    current_user: Annotated[User, Depends(get_current_active_user)],\n) -> User:\n    return current_user\n\n\n@app.get(\"/users/me/items/\")\nasync def read_own_items(\n    current_user: Annotated[User, Security(get_current_active_user, scopes=[\"items\"])],","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/tiangolo/fastapi/blob/3e8d1526d83a90aaf7d6eb6dc682bf150f180b25/docs_src/security/tutorial005_an_py310.py#L139-L175","documentation":"The /token handler in the scopes tutorial raises HTTP 400 'Incorrect username or password' when authenticate_user is falsy. Note the status inconsistency: tutorial005 uses 400 here whereas tutorial004 used 401 — both share the same authenticate_user (with DUMMY_HASH timing protection) and the same anti-enumeration message.","triggerScenarios":"POST /token (form-encoded) with username not in {johndoe, alice} or a password that fails argon2 verification. The form may also carry a scope field, but a credential failure short-circuits before scopes matter.","commonSituations":"Wrong password; missing username field; regenerated argon2 hashes; sending JSON instead of form data.","solutions":["POST username + password matching a seeded user (johndoe or alice with their original plaintext).","If you changed the hasher, regenerate the stored argon2 hashes with get_password_hash.","Standardize the status code on 401 with WWW-Authenticate for consistency with RFC 6749 and tutorial004."],"exampleFix":"// before\nif not user:\n    raise HTTPException(status_code=400, detail=\"Incorrect username or password\")\n\n// after\nif not user:\n    raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=\"Incorrect username or password\", headers={\"WWW-Authenticate\": \"Bearer\"})","handlingStrategy":"validation","validationCode":"import httpx\ndef post_token(client: httpx.Client, u: str, p: str, scopes: str = \"\"):\n    assert u and p, \"username/password required\"\n    data = {\"username\": u, \"password\": p}\n    if scopes:\n        data[\"scope\"] = scopes\n    return client.post(\"/token\", data=data)","typeGuard":"from typing import TypeGuard\ndef is_valid_creds(pair: tuple) -> TypeGuard[tuple[str, str]]:\n    u, p = pair\n    return isinstance(u, str) and isinstance(p, str) and u.strip() and p","tryCatchPattern":"import httpx\ntry:\n    r = httpx.post(\"/token\", data={\"username\": u, \"password\": p, \"scope\": \"me items\"})\n    r.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code in (400, 401):\n        show_generic_login_error()","preventionTips":["POST credentials form-encoded and request all needed scopes in the 'scope' field.","Regenerate stored hashes when the hasher changes.","Treat the credential error as final for those credentials."],"tags":["fastapi","authentication","oauth2","scopes","python"],"backgroundTag":null,"analyzedSha":"3e8d1526d83a90aaf7d6eb6dc682bf150f180b25","analyzedAt":"2026-08-11T02:34:52.986Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}