Authentication / Authorization on routes without JWT and applying scopes #10388
-
First Check
Commit to Help
Example Codeimport typing
from typing import List
import uvicorn
from fastapi import Depends, FastAPI, Request, Security
from fastapi.security import APIKeyHeader, APIKeyQuery
from pydantic import BaseModel, Field
from starlette.authentication import AuthCredentials
from starlette.authentication import requires
class ProfileModel(BaseModel):
id: int = None
name: str = None
token: str = None
permissions: List[str] = Field(default_factory=list)
async def find_by_token(token: str = None) -> ProfileModel:
"""using a fake response here. this needs to get the user from the db based on the token"""
print("find_by_token")
if token == "abc":
return ProfileModel(
id=1,
name="Big noob",
token=token,
permissions=["a", "b"]
)
return ProfileModel()
QUERY_API_KEY = APIKeyQuery(name='token', auto_error=False, description="First")
HEADER_API_KEY = APIKeyHeader(name='X-API-Key', auto_error=False, description="Second")
async def user_dependency(
request: Request,
query_api_key: str = Security(QUERY_API_KEY),
header_api_key: str = Security(HEADER_API_KEY),
) -> ProfileModel:
""" using the token we find the current user """
print("user_dependency")
token = next(
(arg for arg in [query_api_key, header_api_key] if arg is not None),
None
)
print(f"user_dependency: {token}")
user = ProfileModel()
if token:
user = await find_by_token(token=token)
# user = ProfileModel(name="dog", token=str(token), permissions=['c'])
request.scope["auth"] = AuthCredentials(scopes=user.permissions)
request.scope["user"] = user
request.scope["token"] = token
# print(request.scope.get("auth").scopes)
return user
CurrentUser = typing.Annotated[ProfileModel, Depends(user_dependency, use_cache=False)]
app = FastAPI(
debug=True,
title="Demo",
description="Demo FastAPI application",
docs_url="/docs",
)
@app.middleware("http")
async def request_middleware(request: Request, call_next):
print("request_middleware")
# how do i get the token from "user_dependency" here?
user: ProfileModel = await find_by_token("abc")
request.scope['auth'] = AuthCredentials(scopes=user.permissions)
request.scope['user'] = user
return await call_next(request)
@app.get("/")
async def home():
"""Free for all"""
return "home"
# im purly adding this in to be able to print out the marker
_CallableType = typing.TypeVar("_CallableType", bound=typing.Callable)
def permission(
scopes: typing.Union[str, typing.Sequence[str], None] = None,
status_code: int = 403,
redirect: typing.Optional[str] = None,
) -> typing.Callable[[_CallableType], _CallableType]:
if scopes is None:
scopes = []
print(f"permission decorator: {scopes}")
return requires(scopes=scopes, status_code=status_code, redirect=redirect)
@app.get("/profile")
# starlet has a "requires" decorator for routes to check the scopes, ive just wrapped it in permission above to
# be able to print out the values
@permission("abc")
async def profile(
request: Request, # this is required for the auth middleware stuff to be included request.scope[...]
user: CurrentUser # needed so that openapi shows the lock icon
):
"""only logged in users"""
print("profile page")
return request.user
if __name__ == "__main__":
uvicorn.run("main:app", reload=True) Descriptionsome things ive noticed and comments
so in the interim im playing around with the following but it feels like a hack'n'slash
but this wouldn't be available to any automatic documentation items (custom open api and run through routes).
i tried adding a "permission" parameter on the @app.get but that naturally complains about get not having a param defined for permissions. suggest adding **kwargs to all the route stuff (get,post,put etc) that way the devs can do whatever they want with it but it gives the dev the power to add some stuff in. (i noticed that the methods were duplicated in application and routing) ive tried many things now :( but still not happy with it :( how have others solved this? a lot of what i find around auth just regurgitates the jwt examples on the fastapi docs page. was a mission to even find the usage of Operating SystemLinux, Windows Operating System DetailsNo response FastAPI Version0.103.2 Pydantic Version2.4.2 Python VersionPython 3.12.0 Additional ContextNo response |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 7 replies
-
and to further add. as far as i can tell it wont be possible for a Depends to add a "response" (so like auto adding a "403" response to pages that use user: CurrentUser |
Beta Was this translation helpful? Give feedback.
-
hI @WilliamStam , this might help you sort some things out: |
Beta Was this translation helpful? Give feedback.
-
since its a normal dependency you can use it on the router / route / app levels you can also add the "permissions" object onto the route object ala decorator. as in route_object.permissions = xyz but that means you havent explicitly declared it on the object., just attaching to the object. you can also look at #10692 but its very similer to chris's approach for the auth |
Beta Was this translation helpful? Give feedback.
hI @WilliamStam , this might help you sort some things out:
https://github.com/chrisK824/fastapi-rbac-example