Scopes¶
Declare the scopes a route needs with Security(source, scopes=[...]). A validator with a scopes parameter receives them:
from fastapi import FastAPI, Security
from fastapi_multiauth import (
ForbiddenError,
HTTPBearerAuth,
UnauthorizedError,
hash_token,
)
TOKENS = {
hash_token("writer-token"): ("alice", {"posts:read", "posts:write"}),
hash_token("reader-token"): ("bob", {"posts:read"}),
}
async def validate_token(token: str, scopes: list[str]) -> str:
row = TOKENS.get(hash_token(token))
if row is None:
raise UnauthorizedError()
username, granted = row
if not set(scopes) <= granted:
raise ForbiddenError("Missing scope")
return username
bearer = HTTPBearerAuth(validator=validate_token)
app = FastAPI()
@app.get("/posts")
async def list_posts(username: str = Security(bearer, scopes=["posts:read"])):
return []
@app.post("/posts")
async def create_post(username: str = Security(bearer, scopes=["posts:write"])):
return {"author": username}
Raise ForbiddenError (403) for a missing scope: the token is valid, it just cannot do this. JWTValidator does this check for you.
Fails closed¶
If a route declares scopes but the validator has no scopes parameter, the request fails with an error instead of skipping the check. A forgotten parameter shows up in your first test, not as an open route in production.
The same goes for MultiAuth: every source must be able to check scopes, otherwise the route would be protected or not depending on which credential the client sent.
In /docs¶
Scopes appear on every route's security requirement. Only the OAuth 2.0 sources can also publish the list of available scopes, which gives /docs its checkboxes.