{"record":{"id":"b7bb8d01417dc191","repo":"tiangolo/fastapi","slug":"incorrect-username-or-password","errorCode":null,"errorMessage":"Incorrect username or password","messagePattern":"Incorrect username or password","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"docs_src/security/tutorial003_an_py310.py","lineNumber":81,"sourceCode":"            detail=\"Not authenticated\",\n            headers={\"WWW-Authenticate\": \"Bearer\"},\n        )\n    return user\n\n\nasync def get_current_active_user(\n    current_user: Annotated[User, Depends(get_current_user)],\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(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):\n    user_dict = fake_users_db.get(form_data.username)\n    if not user_dict:\n        raise HTTPException(status_code=400, detail=\"Incorrect username or password\")\n    user = UserInDB(**user_dict)\n    hashed_password = fake_hash_password(form_data.password)\n    if not hashed_password == user.hashed_password:\n        raise HTTPException(status_code=400, detail=\"Incorrect username or password\")\n\n    return {\"access_token\": user.username, \"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):\n    return current_user\n","sourceCodeStart":63,"sourceCodeEnd":95,"githubUrl":"https://github.com/tiangolo/fastapi/blob/3e8d1526d83a90aaf7d6eb6dc682bf150f180b25/docs_src/security/tutorial003_an_py310.py#L63-L95","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","solutions":["POST with username exactly matching a fake_users_db key (johndoe or alice) and the matching password.","Confirm the form field is named 'username' and the Content-Type is application/x-www-form-urlencoded.","If case-insensitive logins are desired, normalize input with username.strip().lower() before the lookup."],"exampleFix":"// before\nuser_dict = fake_users_db.get(form_data.username)\nif not user_dict:\n    raise HTTPException(status_code=400, detail=\"Incorrect username or password\")\n\n// after\nuser_dict = fake_users_db.get(form_data.username.strip())\nif not user_dict:\n    raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=\"Incorrect username or password\", headers={\"WWW-Authenticate\": \"Bearer\"})","handlingStrategy":"validation","validationCode":"# Validate before POST /token\nKNOWN_USERS = {\"johndoe\", \"alice\"}\ndef valid_login_form(username: str, password: str) -> bool:\n    return (\n        isinstance(username, str) and isinstance(password, str)\n        and username.strip() in KNOWN_USERS\n        and len(password) > 0\n    )","typeGuard":"from typing import TypeGuard\ndef is_non_empty_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})\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code in (400, 401):\n        # treat both branches identically (server hides which failed)\n        show_generic_login_error()","preventionTips":["Trim and (if your domain allows) case-normalize the username before sending.","Send the form field as 'username' (not 'user'/'email') with Content-Type application/x-www-form-urlencoded.","Treat 400 and 401 identically on the client; never infer which field was wrong."],"tags":["fastapi","authentication","oauth2","python","user-enumeration"],"backgroundTag":null,"analyzedSha":"3e8d1526d83a90aaf7d6eb6dc682bf150f180b25","analyzedAt":"2026-08-11T02:34:52.986Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}