/ graphql

Handling Auth in GraphQL (the right way)

I started on my first production app using GraphQL a couple months back. I've just now been getting into the tougher parts of the app. Querying, mutating, and subscribing to things is awesome and trivial. Batching queries and a good way to log in and out of these sort of apps is not. I say this because the only documentation you can find for query batching (more on that in another post) and how to handle auth is not ideal.

Existing solutions

There's only two ways that I have seen repeated over and over by every blog post I can find.

Lock down your GraphQL endpoint

Make a regular post request to a restful login route. Now pass that token on each request and you can now access your GraphQL endpoint. Awesome right? No, not at all really. How can anyone say this is the future if a recommended approach is to use something else for the actual login. I want to use my graphql endpoint for everything!

Check for the user in every resolver

This is an approach close to my own, but mine is a bit more automated. The idea here is something like this:

const authenticated =
  (fn: GraphQLFieldResolver) =>
  (parent, args, context, info) => {
    if (context.user) {
      return fn(parent, args, context, info);
    }
    throw new Error('User is not authenticated');
  };

//resolver
me: compose(authenticated)(getLoggedInUser)

You can see a full example in this post. First off, not sure why they bring in a function to handle composing functions, but the idea is simple. In every resolver that you expect a user to be authenticated for, wrap it in an authenticated function that will first check if a user context exists in your app.

My Solution

Remember when I said this is close to my solution? This keeps everything inside of GraphQL. The problem here is the repetition. All of my app's resolvers are private, except the auth related ones. Instead of putting that in front of every single resolver, I map through all resolvers and wrap them automagically thinks to how GraphQL works.

All resolvers are inside a huge, plain js object of query, mutation, and subscription keys that get called when hitting your api. This means we can do something like this:

import { merge, mapValues } from 'lodash';

const resolvers = merge(User, Company, Vaults, Receipts, Category);

const authenticated = resolver => (parent, args, context, info) => {
  if (context.user) {
    return resolver(parent, args, context, info);
  }
  throw new Error('401: User is not authenticated');
};

export default mapValues(resolvers, resolver =>
  mapValues(resolver, item => {
    if (item.name.match(/auth/)) return item;
    return authenticated(item);
  })
);

We merge together all of our schema, then wrap all things not matching auth as the function name. This means if you have a resolver called auth it would not require the user to exist in the context. You can modify this to fit your needs, but the premise remains the same. Add this higher order function to all private resolvers, and keep it off public ones.

If you are setting the user context with a JWT or a similar method of auth, your authenticated function will look almost identical. Here's a look at my express server that uses express-jwt for anyone not familiar with GraphQL auth at all:

app.use(
  jwt({
    secret: 'secret',
    credentialsRequired: false,
    userProperty: 'user',
  })
);

app.use(
  '/graphql',
  bodyParser.json(),
  graphqlExpress(request => ({
    schema,
    context: {
      user: request.user,
    },
  }))
);

The JWT middleware will add user to the request object if a valid token is present in the header. Modify this to fit your auth solution, but it's going to look similar to this no matter what you come up with!

Conclusion

I think we now have the best of both worlds. We are keeping auth inside of our GraphQL server, and we are not repeating ourselves over and over in our resolvers. Now I need to figure out a non repetitive way to do permissions (aka authorization)! Hopefully Max Stoiber's proposal will become a standard soon and we can build out a permissions object for resolvers to fix that problem.

I will write up a post on how to do query batching with dataloader from Facebook soon. I have found that the docs make it seem overly complicated. After implementing it inside of my own app, I was shocked at how little effort was involved in using it.

Let me know if you have any other ideas, comments, or questions here or @zachcodes. Thanks so much for reading, I hope this has helped someone out there!