Security
GraftQL's router accepts auxiliary hooks that enable access control at multiple levels of the API.
Table Control
In most cases this is all you need: it controls access per table.
permission_getter
If you want the same access-control logic across your API, define a function that receives the model class (a GraftModel subclass), the request context, and the request type (e.g. 'query', 'mutation'), and returns a boolean indicating whether the request is permitted.
async def get_context(request, response):
# In a real app you would derive the user from a JWT or session cookie.
user = {"categories": ["A", "B"], "classes": ["Resource", "Owner"]}
return {"user": user}
async def permission_getter(module_class, context, request_type):
"""Allow access only to models listed in the user's `classes`."""
return module_class.__name__ in context["user"]["classes"]
Note
These hooks should be passed to GraftRouter.
app = FastAPI(title="GraftQL auth tutorial")
router = GraftRouter(
engine=engine,
dl_filter=dl_filter,
query_filter=query_filter,
permission_getter=permission_getter,
context_getter=get_context,
)
app.include_router(router, prefix="/graphql")
for more details about context:Strawberry.
Tip
You can also define permission_getter on a GraftModel subclass to make the permission logic model-specific.
If you set a generic permission_getter in router and also in a model, the model permission_getter has priority above router's permission_getter
Rows Control
For more complex access control (e.g. row-level filtering), you can provide additional query filter hooks to GraftRouter.
query_filter
Adds a filter to every top-level query. In our example:
async def get_context(request, response):
# In a real app you would derive the user from a JWT or session cookie.
user = {"categories": ["A", "B"], "classes": ["Resource", "Owner"]}
return {"user": user}
def query_filter(model, context):
"""Add a WHERE clause to top-level queries based on the requesting user's allowed categories."""
if model.__name__ == "Resource":
return model.category.in_(context["user"]["categories"])
return True
Note
This function should return a SQLAlchemy filter statement.
dl_filter
Post-filters the results returned by DataLoaders (relationship fields). In our example:
async def get_context(request, response):
# In a real app you would derive the user from a JWT or session cookie.
user = {"categories": ["A", "B"], "classes": ["Resource", "Owner"]}
return {"user": user}
def dl_filter(data, context):
"""Post-filter DataLoader results so relationship fields respect category access."""
user = context["user"]
if data and isinstance(data, list) and len(data) > 0 and isinstance(data[0], Resource):
return [d for d in data if d.category in user["categories"]]
return data
Note
It receives the loaded rows and the context, and returns the filtered rows.