graphene-sqlalchemy
graphene-sqlalchemy copied to clipboard
Question: How do you recommend enforcing authorization?
Hello,
I'd like to systematically enforce authorization for nodes and individual fields within the nodes.
Conceptually something like this might work:
class MyNode(AuthZSQLAlchemyObjectType):
class Meta:
model = MyModel
authorize_node_function = node_authorizer
field_auth = dict(
"name": all_authorizer,
"private_things": self_only_authorizer,
)
node_authorizer(model_instance) would get called whenever a new Node of that type is created. Only fields in the field_auth dict would be exposed in node, and then the associated function would be called like resolve_authorizer(model_instance, field_name)
Any opinions on the best way to achieve this?
Hi @hoffrocket !
This is a "hot" subject in the graphene community (here for example) and no "official" answer have been defined yet, so the framework is not opiniated for now. I suggest you to follow the official graphql suggestion to implement the authorization logic on the business layer.
And for a more concrete answer, I have a suggestion from Dan Palmer for you :
We have a fairly lightweight solution for this:
- We define
get_queryseton our base ORM+graphene type.- We require “filter_to_user” and “filter_to_session” methods on those types (they fail at import time otherwise)
get_querysetfunnels through those methods- the root
noderesolver is patched to useget_queryset.This gets us basic authorisation and it’s fairly straightforward to implement. It also ties in nicely with our query optimisation and some other performance stuff.
Thanks @Nabellaleen Those approaches seem inline with what we'd like to do.
Our goals:
- prevent new columns added to a SQLAlchemy model from leaking into the GraphQL schema (by enforcing use of
only_fieldslike whitelist) - associate a role or auth function with each column to enforce authorization rules at query time
- associate a role or auth function with the Node itself to catch things like id and custom fields that are not in the list of columns
We had been solving these concerns by being using only_fields on every Node and then overriding the resolve methods for columns where we need authorization controls. But that has become cumbersome and error prone. We have a lot of boilerplate code that looks like this:
class OurNode(SQLAlchemyObjectType):
class Meta:
only_fields = ("name", "created_at",...)
@self_or_staff_required
def resolve_name(self, info):
return self.name
@staff_required
def resolve_created_at(self, info):
return self.created_at
I've been experimenting with the concept above and it's pretty similar to what @dfee suggests here https://github.com/graphql-python/graphene-django/issues/79#issuecomment-306583068
It seems to work, and I wonder if we can get some consensus around an approach that could be pushed upstream and be generally useful.
One fundamental challenge to overcome in this community is that graphene-django is basically a superset and fork of graphene-sqlalchemy.
This is somewhat old but still open. Has there been any additional development to support this in graphene-sqlalchemy, or are there authorization patterns the community has settled on as workable?
We have an example of doing authorization with sqlalchemy-oso and graphene in this blog post.
This adheres to the "do it at the business layer" principle that the docs recommend, and might be helpful for folks who don't want to insert a ton of additional code into their graphql api.
We'd love to get feedback from people on how we could improve this!
Thank you!