tiangolo/fastapi · error · HTTPException

Not enough permissions

Error message

Not enough permissions

What it means

In the OAuth2 scopes tutorial, `get_current_user` raises HTTP 401 'Not enough permissions' (with `WWW-Authenticate: Bearer scope="..."`) when the token's scopes do not include every scope required by the route's `Security(..., scopes=[...])`. The token itself is valid and the user exists - it just lacks authorization for this endpoint.

Source

Thrown at docs_src/security/tutorial005_an_py310.py:135

        detail="Could not validate credentials",
        headers={"WWW-Authenticate": authenticate_value},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username = payload.get("sub")
        if username is None:
            raise credentials_exception
        scope: str = payload.get("scope", "")
        token_scopes = scope.split(" ")
        token_data = TokenData(scopes=token_scopes, username=username)
    except (InvalidTokenError, ValidationError):
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    for scope in security_scopes.scopes:
        if scope not in token_data.scopes:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Not enough permissions",
                headers={"WWW-Authenticate": authenticate_value},
            )
    return user


async def get_current_active_user(
    current_user: Annotated[User, Security(get_current_user, scopes=["me"])],
):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/token")
async def login_for_access_token(
    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],

View on GitHub (pinned to 42a41db11f)

Solutions

  1. Re-request a token asking for the required scope, e.g. `scope=items` at /token.
  2. For multiple endpoints, request all needed scopes: `scope=me items`.
  3. Confirm the route's required scopes match what the client requests.

Example fix

# before
# token requested with scope=me, then:
GET /users/me/items/   # requires 'items' => 401
# after
# request scope='me items' at /token, then retry
Defensive patterns

Strategy: validation

Validate before calling

def has_scopes(token_scopes, required) -> bool:
    return all(s in token_scopes for s in required)
# before calling /users/me/items/, ensure 'items' is in the scopes you requested

Type guard

def has_required_scope(token_scopes: list[str], required: list[str]) -> bool:
    return set(required).issubset(set(token_scopes))

Try / catch

try:
    r = client.get('/users/me/items/', headers=auth_header(token))
    r.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 401 and e.response.json().get('detail') == 'Not enough permissions':
        token = request_token(scopes=['me','items'])  # widen scopes, retry once
    raise

Prevention

When it happens

Trigger: `GET /users/me/items/` (requires scope `items`) with a token whose `scope` claim is only `me`. The mismatch is detected in the `for scope in security_scopes.scopes:` loop at line 133.

Common situations: Requesting the wrong scopes at /token; frontend assuming a scope it never asked for; route's required scopes changed but client still requests the old set; scope strings differing by case/whitespace.

Related errors


AI-assisted analysis of tiangolo/fastapi@42a41db11f (2026-08-04). Data as JSON: /data/errors/0e5e56ac14b68581.json. Report an issue: GitHub.