Graphene: How to make authentication per query/mutation?

Created on 7 Jun 2018  路  2Comments  路  Source: graphql-python/graphene

I have a Graphene schema defined like this:

class Queries(graphene.ObjectType):
    node = Node.Field()
    all_climate_zones_iecc = FilterableSQLAlchemyConnectionField(ClimateZoneIECCGraphQL)
    climate_zone_iecc = FilterableField(ClimateZoneIECCGraphQL)
    all_climate_zones_doe = FilterableSQLAlchemyConnectionField(ClimateZoneDOEGraphQL)
    climate_zone_doe = FilterableField(ClimateZoneDOEGraphQL)

class Mutations(graphene.ObjectType):
    submit_building = SubmitBuilding.Field()
    submit_building_type = SubmitBuildingType.Field()
    submit_building_image = SubmitBuildingImage.Field()

graphql_schema = graphene.Schema(query=Queries, mutation=Mutations)

How can I make selective authentication (Flask) where I will deny user to execute the mutation if he is not logged in, but let queries be public?

I can obviously split this into two endpoints and wrap one of them (mutation endpoint) with Flask decorator but this has downfall of splitting GQL into two endpoints. Ideally I want to retain one universal endpoint and do the auth check in GQL. In case user tried to execute query/mutation he doesn't have permissions for, I want to return JSON error through the GQL.

Most helpful comment

@paunovic you can do the authentication at the mutation level. In the def mutate function you can inspect the request context and determine if the user is allowed to perform that mutation. You would also define what error gets returned if the user is not allowed to perform the mutation.

At work use the following decorator to remove some of the boilerplate code from the mutate function:

def anonymous_return(value):
    def anonymous_return_decorator(func):
        def anonymous_return_wrapper(obj, info, **kwargs):
            if not info.context.user.is_authenticated():
                if callable(value):
                    return value()
                return value
            return func(obj, info, **kwargs)
        return anonymous_return_wrapper
    return anonymous_return_decorator

And we have a common AuthenticationError type which looks like this:

class AuthenticationRequired(graphene.ObjectType):
    message = graphene.String(
        required=True,
    )

    @staticmethod
    def default_message():
        return AuthenticationRequired(
            message="You must be logged in to perform this action"
        )

Then our mutations look like this:

class AddCredit(graphene.Mutation):
    class Arguments:
        amount = graphene.Int(required=True)

    Output = AddCreditPayload

    @anonymous_return(AuthenticationRequired.default_message)
    def mutate(self, info, amount):
        # User is authenticated now

You can of course expand on this to build in any kind of arbitrary permission logic but this is a basic example.

All 2 comments

@paunovic you can do the authentication at the mutation level. In the def mutate function you can inspect the request context and determine if the user is allowed to perform that mutation. You would also define what error gets returned if the user is not allowed to perform the mutation.

At work use the following decorator to remove some of the boilerplate code from the mutate function:

def anonymous_return(value):
    def anonymous_return_decorator(func):
        def anonymous_return_wrapper(obj, info, **kwargs):
            if not info.context.user.is_authenticated():
                if callable(value):
                    return value()
                return value
            return func(obj, info, **kwargs)
        return anonymous_return_wrapper
    return anonymous_return_decorator

And we have a common AuthenticationError type which looks like this:

class AuthenticationRequired(graphene.ObjectType):
    message = graphene.String(
        required=True,
    )

    @staticmethod
    def default_message():
        return AuthenticationRequired(
            message="You must be logged in to perform this action"
        )

Then our mutations look like this:

class AddCredit(graphene.Mutation):
    class Arguments:
        amount = graphene.Int(required=True)

    Output = AddCreditPayload

    @anonymous_return(AuthenticationRequired.default_message)
    def mutate(self, info, amount):
        # User is authenticated now

You can of course expand on this to build in any kind of arbitrary permission logic but this is a basic example.

Magical. Thank you.

Was this page helpful?
0 / 5 - 0 ratings