{"record":{"id":"f4558192af60395d","repo":"tiangolo/fastapi","slug":"inactive-user","errorCode":null,"errorMessage":"Inactive user","messagePattern":"Inactive user","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"docs_src/security/tutorial003_an_py310.py","lineNumber":73,"sourceCode":"    return user\n\n\nasync def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):\n    user = fake_decode_token(token)\n    if not user:\n        raise HTTPException(\n            status_code=status.HTTP_401_UNAUTHORIZED,\n            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(","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/tiangolo/fastapi/blob/3e8d1526d83a90aaf7d6eb6dc682bf150f180b25/docs_src/security/tutorial003_an_py310.py#L55-L91","documentation":"Raised by the get_current_active_user dependency in FastAPI's OAuth2 tutorial (fake-hash, no JWT). After get_current_user resolves the token to a real User, line 72 checks current_user.disabled and rejects the request with HTTP 400 'Inactive user' when that flag is truthy. It is a post-authentication gate: the credential was valid, but the account is administratively disabled. The tutorial uses status 400; production code typically uses 403 Forbidden for this case.","triggerScenarios":"Any authenticated request whose dependency chain ends at get_current_active_user (e.g. GET /users/me) when the resolved user has disabled=True. In this file fake_decode_token uses the token verbatim as the username key, so a request with Authorization: Bearer alice resolves to alice (disabled=True at line 20) and trips line 73.","commonSituations":"Using the seeded 'alice' fixture for a demo; accounts an admin suspended/banned; users who have not completed email verification modelled via the disabled flag; tests that forgot to set disabled=False after reactivating a fixture user.","solutions":["Authenticate and send a token for an active user (disabled=False), e.g. the seeded 'johndoe'.","Set fake_users_db[<user>]['disabled'] = False and re-issue the token, because get_current_user caches no state.","In production, return 403 Forbidden (not 400) for disabled-but-authenticated users, and re-issue tokens only after explicit reactivation."],"exampleFix":"// before\nif current_user.disabled:\n    raise HTTPException(status_code=400, detail=\"Inactive user\")\n\n// after\nif current_user.disabled:\n    raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=\"Inactive user\")","handlingStrategy":"try-catch","validationCode":"# Client-side: you cannot reliably know disabled state without calling, but for the\n# tutorial's fake hasher you know the disabled set ahead of time.\nDISABLED_USERS = {\"alice\"}\ndef looks_active(token_or_username: str) -> bool:\n    return token_or_username not in DISABLED_USERS","typeGuard":"from typing import TypeGuard\nfrom typing_extensions import TypedDict\nclass _User(TypedDict):\n    username: str\n    disabled: bool\ndef is_active_user(u: _User) -> TypeGuard[_User]:\n    return not u.get(\"disabled\", False)","tryCatchPattern":"# httpx client\nimport httpx\ntry:\n    r = httpx.get(\"/users/me\", headers={\"Authorization\": f\"Bearer {token}\"})\n    r.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 400 and e.response.json().get(\"detail\") == \"Inactive user\":\n        # prompt re-auth / reactivation flow\n        ...","preventionTips":["Track disabled state in the user record and surface it to admins before they hand out tokens.","Invalidate existing tokens when an account is disabled (token revocation / short TTL).","Use 403 Forbidden for disabled accounts so clients can distinguish it from malformed requests."],"tags":["fastapi","authentication","authorization","python","oauth2"],"backgroundTag":null,"analyzedSha":"3e8d1526d83a90aaf7d6eb6dc682bf150f180b25","analyzedAt":"2026-08-11T02:34:52.986Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}