I’m using Ariadne GraphQL as sub-app to a FastAPI app, you will find the whole code here https://github.com/zero-shubham/friteup-graphql-api
Generally with RestAPI we make endpoints restricted, but with GraphQL APIs we have only one endpoint to which requests are made. Now making this single endpoint (in this case it’s
/graphql) restricted is simple, but what if you don’t want that rather you want specific resolvers (functions that return responses to queries and mutations) to be restricted?
Will try to answer that, before that let’s first understand the current setup of our application.
Now trying to answer our question, the next logical step will be to add authentication inside our sub-app. We will add a JWT token to the response cookie, this will httpOnly secure cookie.
To do so we will add a login resolver with a similar code as the following - https://pastebin.com/UqEhtF1T
Check if such a user exists, match the password if the password matches authenticate the user by adding token to the response as cookie.
That seems done and a plausible solution but when you try that it won’t work, and our GraphQL app will throw an error saying the response doesn’t match the specified schema.
Our GraphQL app is expecting a dictionary with keys same as that of the defined schema, but when we return a response object instead a lot keys get returned. All the headers and cookies are present there and adding these to the schema makes no sense. So, the current scenario seems as -
Only way to add a cookie to the response is, if it gets added after GraphQL sub-app responds, so technically a token is added to the response cookie by FastAPI app.
To do so we need to communicate this to FastAPI, but how? Definitely using middlewares and decorators (higher - order functions).
Since, we are using ASGI server at any point of time the app instance may handle more than one request we have to consider that while coming up with a solution. The best solution I can think of is to maintain a dictionary of requests and their corresponding token, which will be later used when sending back a response.
So, to visualise the scenario now it should as following -
If you could refer to application.py, I have used different middlewares so that concerns can be separated. As the name suggests
cookie_set middleware does the latter which is checks for request id (UUID assigned to every request object) whether there is any token if so adds it to the response. The other middleware
BasicAuthBackend checks every request for any cookie header “Authorization” which has a token, uses it to authenticate the request. Also add a request id to the request object and add these request id and token to the dictionary which maintains tokens for each request.
Now if you are confused why we need to maintain a dictionary for this, well because we have to use this dictionary in multiple function calls. For example, inside “login” resolver where there will be no token already existing inside the request object, so this resolver function will assign a token to that dictionary using the request id. For reference check resolvers/user.py
If you look closely I’m also using a decorator here
authentication_required this is similar to something like jwt_required if you are familiar with Flask apps. Refer to this middleware.py file, you will find that higher-order function there. So, whenever we add that decorator before any resolver function this gets called and if the request is not authenticated it will return without calling the resolver function.
That was it, I may not have been able to explain it all. So, feel free to ask your doubts or even any suggestions that will make this whole setup simpler than what it is. Thanks!