tiangolo/fastapi · error · HTTPException

Not authorized

Error message

Not authorized

What it means

FastAPI returns HTTP 403 "Not authorized" when `session.get(User, user_id)` returns no row for the given id. The handler deliberately uses 403 rather than 404 to avoid leaking which user ids exist. The dependency is attached to the /generate route via `dependencies=[Depends(get_user)]`, so it runs before the StreamingResponse begins.

Source

Thrown at docs_src/dependencies/tutorial013_an_py310.py:27


class User(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str


app = FastAPI()


def get_session():
    with Session(engine) as session:
        yield session


def get_user(user_id: int, session: Annotated[Session, Depends(get_session)]):
    user = session.get(User, user_id)
    if not user:
        raise HTTPException(status_code=403, detail="Not authorized")


def generate_stream(query: str):
    for ch in query:
        yield ch
        time.sleep(0.1)


@app.get("/generate", dependencies=[Depends(get_user)])
def generate(query: str):
    return StreamingResponse(content=generate_stream(query))

View on GitHub (pinned to 42a41db11f)

Solutions

  1. Supply a user_id that exists in the User table (create it first if needed).
  2. Run SQLModel metadata creation / migrations so the table is populated.
  3. Verify the engine URL points at the intended database.
  4. If leaking existence is acceptable for your threat model, switch to 404 for clarity.

Example fix

# before
user = session.get(User, user_id)
if not user:
    raise HTTPException(status_code=403, detail="Not authorized")

# after (optional, if 404 semantics preferred)
user = session.get(User, user_id)
if not user:
    raise HTTPException(status_code=404, detail="User not found")
Defensive patterns

Strategy: validation

Validate before calling

# Verify user existence before calling /generate
exists = users_table.get(user_id)
if not exists:
    raise PermissionError("Not authorized")
client.get("/generate", params={"user_id": user_id, "query": q})

Type guard

def user_exists(session, user_id: int) -> bool:
    return session.get(User, user_id) is not None

Try / catch

r = client.get("/generate", params={...})
if r.status_code == 403:
    # treat as unknown/unauthorized user; do not leak existence
    ...

Prevention

When it happens

Trigger: GET /generate?query=...&user_id=<nonexistent> (or where the user_id query param does not map to a persisted User row).

Common situations: Clients passing an unauthenticated/typo'd user_id; the DB has no matching row; the SQLModel table not created/migrated; wrong DB connection string pointing at an empty database.

Related errors


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