question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Question] Add middleware to GraphQLApp

See original GitHub issue

I’m wondering if it’s possible to add middlewares to a GraphQLApp directly, for example is it possible to add authentication on top of it?

I think I could make it work using the following strategy, but is there a better way?

def authentication_check(func):

    async def check(*args, **kwargs):
        print("We could check for authentication here")
        response = await func(*args, **kwargs)
        print("...")
        return response

    return check


graphql_app = GraphQLApp(schema=schema, executor_class=AsyncioExecutor)
graphql_app.handle_graphql = authentication_check(graphql_app.handle_graphql)

Maybe it would be better to intercept calls to graphql_app.execute so we can decide which request are allowed depending on the kind of user? Or, as suggested in graphene documentation we could just allow to add middleware directly to schema.execute right here.

async def execute(  # type: ignore
        self, query, variables=None, context=None, operation_name=None, middleware=None
    ):

    return await self.schema.execute(query, middleware=middleware, ...)

Or maybe even better, allow the user to pass any parameter to schema.execute:

async def execute(  # type: ignore
        self, query, **optional_args
    ):

    return await self.schema.execute(query, **optional_args)

I found this location to add a middleware very strange (adding it on the schema directly would make more sense to me), but if that’s what graphene does I guess that’s the better way? I’m very new to this so maybe I’m just lost ^^.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:9 (2 by maintainers)

github_iconTop GitHub Comments

9reactions
tazimmermancommented, Mar 5, 2020

Here’s what I did, in case anyone finds it useful (or has a better suggestion). I derived my own app from GraphQLApp, and re-implemented execute to inject the middleware.

class CustomGraphQLApp(GraphQLApp):
    """App that injects our custom directive middleware when calling `schema.execute`."""

    middleware = [CustomDirectiveMiddleware()]

    async def execute(self, query, variables=None, context=None, operation_name=None):
        if self.is_async:
            return await self.schema.execute(
                query,
                variables=variables,
                operation_name=operation_name,
                executor=self.executor,
                return_promise=True,
                context=context,
                middleware=self.middleware,
            )
        else:
            raise NotImplementedError("Synchronous execution is not supported.")
1reaction
cglacetcommented, Dec 27, 2019

I’m still investigating this, but another (probably better) option to check for authorization is what I suggested in the original question: Modify the graphQL schema on the flight to add a middlewares directly to schema.execute:


def add_execute_middleware(graphql_schema, middleware):

    def graphql_middleware_decorator(graphql_execute):

        def execute_with_middleware(*args, **kwargs):
            return graphql_execute(*args, **kwargs, middleware=middleware)

        return execute_with_middleware

    graphql_schema.execute = graphql_middleware_decorator(graphql_schema.execute)
    return graphql_schema


async def graphql_auth_middleware(next, root, info, **kwargs):
    authorization = info.context['request'].user.authorization_group
    if authorization != 'admin' and 'customersList' in info.path:
        raise ValueError("customersList is not available for your group.")
    if info.field_name == 'password':
        return "even if it's encrypted, we wont send this to you."
    return await next(root, info, **kwargs)


schema = add_execute_middleware(schema, [graphql_authorization_middleware])
graphql_app = GraphQLApp(schema=schema)

This way we can access to parsed graphQL queries, which allows us to get more accurate information faster. For example we just need to search a list to check things like 'customersList' in info.path.

Here is another example limiting the maximum query depth (like some people tend to do):

async def graphql_max_depth_middleware(next, root, info, **kwargs):
    if len(info.path) > GRAPHQL_MAX_QUERY_DEPTH:
        raise ValueError("GraphQL request is too complex.")
    return await next(root, info, **kwargs)

schema = add_execute_middleware(schema, [graphql_authorization_middleware, graphql_max_depth_middleware])
graphql_app = GraphQLApp(schema=schema)

This also have the benefit or returning meaningful results to the client:

{
    "data": {
        "customer": {
            "lastname": "glacet",
            "password": "even if it's encrypted, we wont send this to you."
        },
        "customers": {
            "edges": [
                {
                    "node": null
                },
            ]
        },
        "customersList": null
    },
    "errors": [
        {
            "message": "customersList is not available for your group.",
            "locations": [  ],
            "path": ["customersList"]
        },
        {
            "message": "GraphQL request is too complex.",
            "locations": [  ],
            "path": ["customers", "edges", 0, "node"]
        },
    ]
}

That would be pretty easy to update starlette to allow for such middleware to be passed directly to GraphQLApp.


Edit I noticed I should probably use Starlette’s scopes for user groups authorizations, but that’s rather not clear to me. It would only require to update the code like this:

async def graphql_auth_middleware(next, root, info, **kwargs):
    user_scopes = info.context['request'].auth.scopes
    if 'admin' not in user_scopes and 'customersList' in info.path:
        raise ValueError("customersList is not available for your group.")
   ...

And have credentials adding user groups like so: AuthCredentials(["authenticated", authorization_group]).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Authentication and Express Middleware - GraphQL
To use middleware with a GraphQL resolver, just use the middleware like you would with a normal Express app. The request object is...
Read more >
Treating GraphQL directives as middleware - LogRocket Blog
GraphQL Middleware allows you to implement all your middleware logic in your code, whereas directives encourage you to mix schema with your ...
Read more >
graphql-middleware - npm
GraphQL Middleware is a schema wrapper which allows you to manage additional functionality across multiple resolvers efficiently.
Read more >
Use graphql-middleware for cross-cutting concerns | Medium
GraphQL Middleware is a schema wrapper which allows you to manage additional functionality across multiple resolvers efficiently. They are applied in an inward ......
Read more >
Open Sourcing GraphQL Middleware - Library to Simplify Your ...
GraphQL Middleware lets you run arbitrary code before or after a resolver is invoked. It improves your code structure by enabling code reuse ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found