affaan-m/ECC · error · HTTPException
Not authorized
Error message
Not authorized
What it means
In `update_user`, before delegating to the service, the handler checks `current_user.id != user_id` and raises `HTTPException(403, "Not authorized")`. This is an ownership check: a user may only patch their own record. There is no admin override in this snippet.
Source
Thrown at skills/fastapi-patterns/SKILL.md:276
db: DbDep,
current_user: ActiveUserDep,
skip: Annotated[int, Query(ge=0)] = 0,
limit: Annotated[int, Query(ge=1, le=100)] = 20,
) -> UserListResponse:
service = UserService(db)
users, total = await service.list(skip=skip, limit=limit)
return UserListResponse(total=total, items=users)
@router.patch("/{user_id}", response_model=UserResponse)
async def update_user(
user_id: int,
payload: UserUpdate,
db: DbDep,
current_user: ActiveUserDep,
) -> UserResponse:
if current_user.id != user_id:
raise HTTPException(status_code=403, detail="Not authorized")
service = UserService(db)
try:
user = await service.update(user_id, payload)
except DuplicateUserError:
raise HTTPException(status_code=400, detail="Email already registered")
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user
@router.post("/token")
async def login(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
db: DbDep,
) -> dict[str, str]:
service = UserService(db)
token = await service.authenticate(form_data.username, form_data.password)
if token is None:View on GitHub (pinned to 01e15490f0)
Solutions
- Front-end: derive `{user_id}` from the JWT-decoded `current_user.id`, never from a route param the user can tamper with.
- If admins must edit others, add a role check: `if current_user.id != user_id and not current_user.is_admin:`.
- Return 403 silently — do not leak whether `user_id` exists.
- Consider `PATCH /me` as the primary self-edit route and restrict `/{user_id}` to admins.
Example fix
# before
if current_user.id != user_id:
raise HTTPException(status_code=403, detail="Not authorized")
# after — allow admins, otherwise restrict to self
if current_user.id != user_id and not current_user.is_admin:
raise HTTPException(status_code=403, detail="Not authorized") Defensive patterns
Strategy: validation
Validate before calling
# derive the route from the JWT, never from user input
const me = await client.get('/users/me')
await client.patch(`/users/${me.id}`, payload) Type guard
null
Try / catch
from fastapi import HTTPException
try:
await client.patch(f'/users/{uid}', json=payload)
except HTTPException as e:
if e.status_code == 403:
show_error('You can only edit your own account.')
raise Prevention
- Prefer a `PATCH /me` route for self-edits; gate `/{user_id}` behind admin role.
- Never let the client choose another user's id from a URL param.
- Add an integration test that a non-owner gets 403.
When it happens
Trigger: Authenticated user calls `PATCH /users/{user_id}` with a `user_id` that is not their own (e.g. `/users/5` when their JWT subject is `12`). No admin role exists in this handler to bypass the check.
Common situations: Front-end constructs the URL from a stale user id (cached after re-login). Client tries to edit another user. Tests reuse one token for many user ids.
Related errors
- Inactive user
- Passwords do not match
- Email already registered
- Incorrect username or password
- --no-browser is valid only for ecc ito login; auth is valida
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/34c37b51d0c566a9.
Report an issue: GitHub.