{"record":{"id":"bb875db63562b689","repo":"affaan-m/ECC","slug":"incorrect-username-or-password","errorCode":null,"errorMessage":"Incorrect username or password","messagePattern":"Incorrect username or password","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"skills/fastapi-patterns/SKILL.md","lineNumber":295,"sourceCode":"    service = UserService(db)\n    try:\n        user = await service.update(user_id, payload)\n    except DuplicateUserError:\n        raise HTTPException(status_code=400, detail=\"Email already registered\")\n    if user is None:\n        raise HTTPException(status_code=404, detail=\"User not found\")\n    return user\n\n\n@router.post(\"/token\")\nasync def login(\n    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],\n    db: DbDep,\n) -> dict[str, str]:\n    service = UserService(db)\n    token = await service.authenticate(form_data.username, form_data.password)\n    if token is None:\n        raise HTTPException(\n            status_code=status.HTTP_401_UNAUTHORIZED,\n            detail=\"Incorrect username or password\",\n            headers={\"WWW-Authenticate\": \"Bearer\"},\n        )\n    return {\"access_token\": token, \"token_type\": \"bearer\"}\n```\n\n---\n\n## Service Layer\n\n```python\n# app/services/user_service.py\nfrom datetime import datetime, timedelta, timezone\n\nfrom jose import jwt\nfrom passlib.context import CryptContext\nfrom sqlalchemy import func, select","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/skills/fastapi-patterns/SKILL.md#L277-L313","documentation":"In the `login` route (OAuth2 password flow), `UserService.authenticate(username, password)` returns `None` when credentials do not validate; the handler raises `HTTPException(401, \"Incorrect username or password\", headers={\"WWW-Authenticate\":\"Bearer\"})`. The deliberate vague message does not distinguish \"no such user\" from \"wrong password\" to prevent user enumeration.","triggerScenarios":"POST `/token` with `OAuth2PasswordRequestForm` whose `username` does not exist OR whose `password` does not hash-match. Either case yields `token is None` → 401.","commonSituations":"Wrong password typo. User registered with email but tries username. Account was deleted. Timing-attack-prone compare in `authenticate` (use `secrets.compare_digest` / `pwd_ctx.verify`).","solutions":["Front-end: show the same \"invalid credentials\" message for both unknown-user and wrong-password; never reveal which.","Ensure `UserService.authenticate` runs the password hash verify even when the user is not found, so timing does not leak existence.","Confirm the client sends `application/x-www-form-urlencoded` with `username` and `password` fields (OAuth2 form), not JSON.","Check rate limiting on `/token` to slow brute force."],"exampleFix":"# before\ntoken = await service.authenticate(form_data.username, form_data.password)\nif token is None:\n    raise HTTPException(status_code=401, detail=\"Incorrect username or password\", headers={\"WWW-Authenticate\":\"Bearer\"})\n\n# after — identical path; add brute-force throttle\nfrom slowapi import Limiter\n@router.post(\"/token\")\n@limiter.limit(\"5/minute\")\nasync def login(request: Request, form_data: ...):\n    ...\n    if token is None:\n        raise HTTPException(status_code=401, detail=\"Incorrect username or password\", headers={\"WWW-Authenticate\":\"Bearer\"})","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"from fastapi import HTTPException\ntry:\n    token = await client.post('/token', data={'username':u,'password':p})\nexcept HTTPException as e:\n    if e.status_code == 401:\n        show_error('Incorrect username or password')  # do NOT reveal which\n    raise","preventionTips":["Run the password verify even when the user is missing so timing is uniform.","Use `secrets.compare_digest`/passlib for the compare.","Rate-limit `/token` (e.g. slowapi 5/min/IP) to blunt brute force."],"tags":["fastapi","auth","login","http-401","security"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}