Advanced GraphQL: Authorization in GraphQL #14

In this post, we'll talk about how you might go about authentication and authorization when using GraphQL.


At some point (probably pretty early on) when building a GraphQL endpoint, you’ll probably have to face the question of how to control who can see and interact with the data in your API.


Authentication is determining whether a user is logged in or not, and subsequently figuring out which user someone is. Authorization is then deciding what the user has permission to do or see.

The GraphQL specification doesn’t tell you how to do authentication or authorization. That’s a good thing because it lets you use any technology you like, but it can be a bit confusing to developers who are new to GraphQL.

This article will primarily be focusing on how to set up authorization for your schema once you know about the user trying to make the request, but we’ll go through one example of authentication just to get some context for what we’re doing.
. . .

Authentication in GraphQL

Unless your GraphQL API is completely public, your server will need to authenticate its users. Your options are twofold:
  1. Handle authentication in GraphQL itself.
  2. Let the web server (e.g. express or nginx) take care of authentication.

Both of these options have some advantages and some disadvantages.
If you do authentication in the web server, you can use a standard auth package (e.g. passport.js for express) and many existing authentication methods will work out of the box. You can also add and remove methods at your liking without modifying the GraphQL schema.

However, doing authentication in the web server has the downside of requiring the client to speak to an authentication endpoint in addition to the GraphQL endpoint.
. . .

Let’s start with the first option: doing authentication in the GraphQL resolvers.

Putting user info on the context

Authorization is a type of business logic that describes whether a given user/session/context has permission to perform an action or see a piece of data. For example:
“Only authors can see their drafts”

Enforcing this kind of behavior should happen in the business logic layer. It is tempting to place authorization logic in the GraphQL layer like so:
var postType = new GraphQLObjectType({ name: ‘Post’, fields: { body: { type: GraphQLString, resolve: (post, args, context, { rootValue }) => { // return the post body only if the user is the post's author if (context.user && (context.user.id === post.authorId)) { return post.body; } return null; } } } });

Notice that we define “author owns a post" by checking whether the post's authorId field equals the current user’s id. Can you spot the problem? We would need to duplicate this code for each entry point into the service.

Then if the authorization logic is not kept perfectly in sync, users could see different data depending on which API they use. Yikes! We can avoid that by having a single source of truth for authorization.

Defining authorization logic inside the resolver is fine when learning GraphQL or prototyping. However, for a production codebase, delegate authorization logic to the business logic layer. Here’s an example:
var postRepository = require('postRepository'); var postType = new GraphQLObjectType({ name: ‘Post’, fields: { body: { type: GraphQLString, resolve: (post, args, context, { rootValue }) => {
//Authorization logic lives inside postRepository return postRepository.getBody(context.user, post); } } } });

In the example above, we see that the business logic layer requires the caller to provide a user object. If you are using GraphQL.js, the User object should be populated on the context argument or rootValue in the fourth argument of the resolver.

It is recommended passing a fully-hydrated User object instead of an opaque token or API key to your business logic layer. This way, we can handle the distinct concerns of authentication and authorization in different stages of the request processing pipeline.
. . .

Doing authentication in the web server. What would that look like?

Passing User Token into Headers
If you’re using a REST API that has built-in authorization, like with an HTTP header, you have one more option. Rather than doing any authentication or authorization work in the GraphQL layer (in resolvers/models), it’s possible to simply pass through the headers or cookies to your REST endpoint and let it do the work.

Here’s an example:
// src/server.js
context: ({ req }) => {
// pass the request information through to the model
return {
user,
models: {
User: generateUserModel({ req }),
...
}
};
},

// src/models/user.js export const generateUserModel = ({ req }) => ({ getAll: () => { return fetch('http://myurl.com/users', { headers: req.headers }); }, });

If your REST endpoint is already backed by some form of authorization, this cuts down a lot of the logic that needs to get built in the GraphQL layer. This can be a great option when building a GraphQL API over an existing REST API that has everything you need already built in.
. . .
This brings us to the end of conceptual GraphQL tutorial series, happy hacking!!

On a mission to build Next-Gen Community Platform for Developers