tiangolo/fastapi · error · HTTPException

Not enough permissions

Error message

Not enough permissions

What it means

Legacy-DI variant of error 51. get_current_user iterates the route's required SecurityScopes against the token's space-delimited 'scope' claim and raises HTTP 401 'Not enough permissions' with WWW-Authenticate: Bearer scope="<required>" when a required scope is missing. Semantics identical to the Annotated version; only the DI syntax differs.

Solutions

  1. Request the required scope at POST /token via the form field scope (e.g. scope='me items').
  2. Re-login to obtain a token containing the needed scope.
  3. Confirm scope names match those declared on OAuth2PasswordBearer.
Defensive patterns

Strategy: validation

Validate before calling

import jwt
REQUIRED = {"items"}
def has_required_scopes(token: str, required: set[str]) -> bool:
    payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    token_scopes = set(payload.get("scope", "").split())
    return required.issubset(token_scopes)

Type guard

from typing import TypeGuard
def token_has_scopes(token: str, need: set[str]) -> TypeGuard[str]:
    import jwt
    p = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    return need.issubset(set(p.get("scope", "").split()))

Try / catch

import httpx
try:
    r = httpx.get("/users/me/items/", headers={"Authorization": f"Bearer {token}"})
    r.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 401:
        need = parse_required_scopes(e.response.headers.get("www-authenticate", ""))
        token = login_with_scopes(need)

Prevention

When it happens

Trigger: Call a route guarded by Security(get_current_user, scopes=['items']) (e.g. GET /users/me/items/) with a token whose scope claim lacks 'items' (e.g. scope=me only).

Common situations: Wrong scopes requested at /token; scope naming mismatch; token minted before scopes existed.

Related errors


AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11). Data as JSON: /api/errors/80a392c0326bc71a. Report an issue: GitHub.

Appendix: source

Thrown at docs_src/security/tutorial005_py310.py:134

        detail="Could not validate credentials",
        headers={"WWW-Authenticate": authenticate_value},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = 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: 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: OAuth2PasswordRequestForm = Depends(),

View on GitHub (pinned to 3e8d1526d8)