{"id":"66a67ff3a698fc86","repo":"tiangolo/fastapi","slug":"not-authenticated","errorCode":null,"errorMessage":"Not authenticated","messagePattern":"Not authenticated","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"docs_src/security/tutorial003_an_py310.py","lineNumber":61,"sourceCode":"\n\ndef get_user(db, username: str):\n    if username in db:\n        user_dict = db[username]\n        return UserInDB(**user_dict)\n\n\ndef fake_decode_token(token):\n    # This doesn't provide any security at all\n    # Check the next version\n    user = get_user(fake_users_db, token)\n    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)","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/tiangolo/fastapi/blob/42a41db11f6882807ac3c057b942178d53b97438/docs_src/security/tutorial003_an_py310.py#L43-L79","documentation":"Raised by `get_current_user` with HTTP 401 when the bearer token cannot be resolved to a user. In this tutorial `fake_decode_token` looks the token up directly as a username in `fake_users_db`, so any token that is not a known username triggers it. The response sets `WWW-Authenticate: Bearer` so clients/OAuth2 flows know to re-authenticate.","triggerScenarios":"`GET /users/me` with `Authorization: Bearer nobody` (unknown username), or a token belonging to a since-removed user record. (A missing/malformed header is caught earlier by `OAuth2PasswordBearer`, which raises the same 401 'Not authenticated'.)","commonSituations":"Stale token cached after logout; token from a different environment; user record deleted; token truncated during copy-paste; in this toy app the token literally equals the username so any typo fails.","solutions":["POST /token with valid form credentials to obtain a fresh access token, then retry.","For this example the token is the username, so send `Authorization: Bearer johndoe`.","Make sure the client sends the full, untruncated token using the `Bearer ` scheme."],"exampleFix":"# before\ncurl -H 'Authorization: Bearer expired' http://localhost:8000/users/me\n# after\nTOKEN=$(curl -s -X POST http://localhost:8000/token -d 'username=johndoe&password=secret' | jq -r .access_token)\ncurl -H \"Authorization: Bearer $TOKEN\" http://localhost:8000/users/me","handlingStrategy":"try-catch","validationCode":"import re\ndef looks_like_bearer(header: str | None) -> bool:\n    return bool(header) and re.fullmatch(r'Bearer \\S+', header, re.I) is not None","typeGuard":"def is_not_authenticated(resp) -> bool:\n    return getattr(resp, 'status_code', None) == 401","tryCatchPattern":"try:\n    me = client.get('/users/me', headers=auth_header(token))\n    me.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 401:\n        token = refresh_login()  # re-login, then retry once\n    else:\n        raise","preventionTips":["Always obtain the token from /token before calling protected endpoints.","Centralize the Authorization header in one client interceptor.","On 401, refresh the token once and retry - do not loop."],"tags":["authentication","oauth2","bearer","security","fastapi"],"analyzedSha":"42a41db11f6882807ac3c057b942178d53b97438","analyzedAt":"2026-08-04T19:23:32.007Z","schemaVersion":2}