tiangolo/fastapi · error · HTTPException
Inactive user
Error message
Inactive user
What it means
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.
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.
Example fix
// before
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
// after
if current_user.disabled:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user") Defensive patterns
Strategy: try-catch
Validate before calling
# Client-side: you cannot reliably know disabled state without calling, but for the
# tutorial's fake hasher you know the disabled set ahead of time.
DISABLED_USERS = {"alice"}
def looks_active(token_or_username: str) -> bool:
return token_or_username not in DISABLED_USERS Type guard
from typing import TypeGuard
from typing_extensions import TypedDict
class _User(TypedDict):
username: str
disabled: bool
def is_active_user(u: _User) -> TypeGuard[_User]:
return not u.get("disabled", False) Try / catch
# httpx client
import httpx
try:
r = httpx.get("/users/me", headers={"Authorization": f"Bearer {token}"})
r.raise_for_status()
except httpx.HTTPStatusError as e:
if e.response.status_code == 400 and e.response.json().get("detail") == "Inactive user":
# prompt re-auth / reactivation flow
... Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11).
Data as JSON: /api/errors/f4558192af60395d.
Report an issue: GitHub.
Appendix: source
Thrown at docs_src/security/tutorial003_an_py310.py:73
return user
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
user = fake_decode_token(token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
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(View on GitHub (pinned to 3e8d1526d8)