FastAPI Backend Development Bootcamp · 课时

基于作用域的授权与角色守卫

使用 OAuth2 作用域和可复用的依赖守卫,按端点强制执行基于角色的访问控制权限。

第 4 / 4 课13 个步骤

基于作用域的授权与角色守卫 是 CoddyKit 上的免费 FastAPI Backend Development Bootcamp 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 FastAPI Backend Development Bootcamp 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 FastAPI Backend Development Bootcamp 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

Authentication vs Authorization

Once a user proves who they are (authentication), you still need to decide what they are allowed to do. That second step is authorization.

  • Authentication answers: who are you? (verify the JWT)
  • Authorization answers: are you permitted to call this endpoint?

In OAuth2, fine-grained permissions are expressed as scopes — short strings like items:read or users:write. Each token carries the scopes that were granted, and each endpoint declares the scopes it requires.

What a Scope Looks Like

A scope is just a label for a permission. By convention they use a resource:action shape, which keeps them readable as your API grows.

  • items:read — list or fetch items
  • items:write — create or update items
  • admin — full administrative access

The JWT stores granted scopes, usually as a space-separated string in a scopes claim. Here is a tiny helper that parses and checks them.

def has_scope(token_scopes: str, required: str) -> bool:
    granted = token_scopes.split()
    return required in granted

claim = "items:read items:write"
print(has_scope(claim, "items:read"))   # True
print(has_scope(claim, "admin"))        # False

Declaring Scopes on the OAuth2 Scheme

FastAPI's OAuth2PasswordBearer accepts a scopes dictionary that documents every scope your API understands. This powers the interactive docs so testers can request specific permissions.

The mapping is { scope_name: human_description }. It does not grant anything by itself — it just describes what exists.

from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="token",
    scopes={
        "items:read": "Read items.",
        "items:write": "Create or update items.",
        "admin": "Full administrative access.",
    },
)

Encoding Granted Scopes Into the JWT

When a user logs in, you decide which scopes they get (often based on their role) and embed them in the token. Store them in a scopes claim so every later request carries the permissions.

The token request form includes a scope field; you should grant only scopes the user is actually entitled to — never blindly echo what the client asked for.

from datetime import datetime, timedelta, timezone
import jwt  # PyJWT

SECRET = "change-me"

def create_token(username: str, scopes: list[str]) -> str:
    payload = {
        "sub": username,
        "scopes": scopes,
        "exp": datetime.now(timezone.utc) + timedelta(minutes=30),
    }
    return jwt.encode(payload, SECRET, algorithm="HS256")

Requiring Scopes with the Security Helper

To require a scope on an endpoint, declare the dependency with Security(...) (not plain Depends) and pass a scopes list. FastAPI collects all required scopes along the dependency tree and exposes them via a SecurityScopes object.

Below, the endpoint demands the items:read scope before read_items ever runs.

from fastapi import Depends, Security, FastAPI

app = FastAPI()

@app.get("/items/")
async def read_items(
    user=Security(get_current_user, scopes=["items:read"]),
):
    return {"owner": user["username"]}

Validating Scopes in the Dependency

The dependency that resolves the current user receives a SecurityScopes argument listing every scope required by the route. You decode the JWT, read the granted scopes, and reject the request if any required scope is missing.

  • Return 401 if the token is invalid or expired.
  • Return 403 if the token is valid but lacks the scope (the user is known but not permitted).
from fastapi import Depends, HTTPException, status
from fastapi.security import SecurityScopes
import jwt

async def get_current_user(
    security_scopes: SecurityScopes,
    token: str = Depends(oauth2_scheme),
):
    try:
        payload = jwt.decode(token, SECRET, algorithms=["HS256"])
    except jwt.PyJWTError:
        raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid token")
    token_scopes = payload.get("scopes", [])
    for scope in security_scopes.scopes:
        if scope not in token_scopes:
            raise HTTPException(
                status.HTTP_403_FORBIDDEN,
                detail=f"Not enough permissions: {scope}",
            )
    return {"username": payload["sub"], "scopes": token_scopes}

The WWW-Authenticate Header

OAuth2 expects a 401 response to include a WWW-Authenticate header describing how to authenticate. When scopes are involved, that header should also list the required scopes so the client knows what to request.

FastAPI's SecurityScopes object builds this string for you via scope_str.

from fastapi.security import SecurityScopes
from fastapi import HTTPException, status

def auth_error(security_scopes: SecurityScopes, detail: str):
    if security_scopes.scopes:
        value = f'Bearer scope="{security_scopes.scope_str}"'
    else:
        value = "Bearer"
    return HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail=detail,
        headers={"WWW-Authenticate": value},
    )

From Scopes to Roles

Scopes are flexible but granular. Most apps also think in roles — admin, editor, viewer — where each role bundles a set of scopes. A clean approach is to map roles to scopes at login time.

This keeps endpoints declaring fine-grained scopes while users are managed by coarse-grained roles.

ROLE_SCOPES = {
    "admin": ["items:read", "items:write", "admin"],
    "editor": ["items:read", "items:write"],
    "viewer": ["items:read"],
}

def scopes_for_role(role: str) -> list[str]:
    return ROLE_SCOPES.get(role, [])

print(scopes_for_role("editor"))  # ['items:read', 'items:write']
print(scopes_for_role("guest"))   # []

A Reusable Role Guard

Sometimes you want to guard by role directly instead of by scope. A guard factory returns a dependency configured for a specific role, so you can reuse it across many endpoints.

Calling require_role("admin") produces a dependency that rejects anyone whose token role is not admin.

from fastapi import Depends, HTTPException, status

def require_role(required_role: str):
    async def guard(user=Depends(get_current_user)):
        if user.get("role") != required_role:
            raise HTTPException(
                status.HTTP_403_FORBIDDEN,
                detail="Insufficient role",
            )
        return user
    return guard

@app.delete("/items/{item_id}")
async def delete_item(item_id: int, user=Depends(require_role("admin"))):
    return {"deleted": item_id, "by": user["username"]}

Guarding by 'Any of' Several Roles

Real endpoints often allow more than one role. Generalize the guard to accept a set of acceptable roles and pass if the user matches any of them.

This pure-Python pattern is easy to unit test without spinning up a server.

def check_access(user_role: str, allowed: set[str]) -> bool:
    return user_role in allowed

print(check_access("editor", {"editor", "admin"}))  # True
print(check_access("viewer", {"editor", "admin"}))  # False
print(check_access("admin", {"admin"}))             # True

Putting It Together

A complete flow looks like this:

  • Login maps the user's role to scopes and signs a JWT.
  • Each endpoint declares required scopes with Security(get_current_user, scopes=[...]).
  • The dependency decodes the token and verifies every required scope is present.
  • Reusable require_role guards handle coarse role checks where scopes are overkill.

Because guards are just dependencies, they compose: an endpoint can require both a scope and a role, and FastAPI runs both before your handler.

@app.post("/admin/reports")
async def make_report(
    admin=Depends(require_role("admin")),
    user=Security(get_current_user, scopes=["admin"]),
):
    return {"status": "generated", "by": user["username"]}

Quick Check

Choose the response that correctly distinguishes authentication failure from authorization failure for scope checks.

Recap

You now know how to enforce per-endpoint permissions in FastAPI:

  • Scopes are granular resource:action permissions stored in the JWT's scopes claim.
  • Declare required scopes with Security(dep, scopes=[...]); FastAPI gathers them into SecurityScopes.
  • The dependency decodes the token, returns 401 for invalid credentials and 403 when a required scope is missing.
  • Add a WWW-Authenticate header (with scope_str) on 401 responses for OAuth2 compliance.
  • Roles bundle scopes; map role to scopes at login, and use a reusable require_role guard factory for coarse role-based access control.

Because guards are ordinary dependencies, they compose cleanly and keep authorization logic out of your handlers.

免费开始

用 AI 导师学习 FastAPI Backend Development Bootcamp — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
21
课程
84

常见问题解答

「基于作用域的授权与角色守卫」课时是免费的吗?

是的 — 「基于作用域的授权与角色守卫」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 FastAPI Backend Development Bootcamp 课程的其余内容,请升级到 CoddyKit PRO。 FastAPI Backend Development Bootcamp 课程共包含 4 节课。

「基于作用域的授权与角色守卫」这节课中我会学到什么?

使用 OAuth2 作用域和可复用的依赖守卫,按端点强制执行基于角色的访问控制权限。 你通过在浏览器中直接运行的动手代码来练习 FastAPI Backend Development Bootcamp,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 FastAPI Backend Development Bootcamp 需要有经验吗?

无需任何先前经验。CoddyKit 上的 FastAPI Backend Development Bootcamp 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「基于作用域的授权与角色守卫」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 FastAPI Backend Development Bootcamp 课中编写并运行代码吗?

能。每节 FastAPI Backend Development Bootcamp 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. OAuth2 密码流程与令牌签发
  2. 使用 python-jose 签名与验证 JWT
  3. 刷新令牌与令牌轮换
  4. 基于作用域的授权与角色守卫
← 返回 FastAPI Backend Development Bootcamp