Authentication and Authorization in GraphQL

Dotan Simha

After a few years of working with GraphQL, as open-source developers and as infrastructure team in large enterprises, we’ve learned some lessons about GraphQL, and how to authenticate and authorize GraphQL API.

Authentication and authorization should be simple, because for most cases, it’s just a piece of code that we wish to run before letting users access certain resources.

In this article we’ll go through all the different ways of implementing authentication and authorization, the benefits and downsides to each one, and offer a solution that we believe is the best philosophy to do so.

Authentication / Authorization?

I found good answer in StackOverflow covers the main differences between authentication and authorization:

StackOverflow Answer

In this article, I’ll cover the difference between authentication and authorization with GraphQL APIs, explain how to implement them with GraphQL server, and with the GraphQL-Modules framework.

We learned that a good implementation for GraphQL authentication has the following features regarding authentication:

  • Your authentication implementation should eventually provide the currentUser to the resolvers
  • Your resolvers should not know about the authentication logic (separation between the business logic and the authentication logic)
  • You wish to protect only parts of your GraphQL schema, and not all of it.
  • You want to be able to authenticate parts of your schema, on a field level.

And the following features regarding authorization:

  • You wish to protect some fields, according to custom rules.
  • You wish to run custom logics that protects parts of your GraphQL schema.
  • Our custom rules should not be coupled to a specific resolver.

Where to Put Your Authentication?

To understand why implementing authentication in GraphQL might be tricky, let’s start with the following chart:

Where to put your authentication?

A request comes from the network, go through an HTTP server (for example, express) — That’s #1 in the chart. Then it goes through GraphQL server, which builds a context, and then it runs your resolvers — #2. Then, you have your business logic, that you might want to separate from the resolvers, that’s #3.

Basically, you can implement GraphQL authentication on each part of this chart. But there are differences between those points that might effect the behaviour of your server, and might limit you later doing some things.

Let’s focus on the features requirements I mentioned before, and try to understand how the implementation selection might affect you.

So let’s take #1: If you implement your authentication on #1 (between the network request and the HTTP server), you probably does it with express middleware or something similar. The benefit in this is the separation between authentication logic and your business logic. But, you can’t connect your data from the HTTP request to your GraphQL server, and it might be difficult later to get access to the currentUser. It also protects the entire GraphQL endpoint, and not only parts of it. So that’s nice, but not perfect.

Regarding #3: Implementing authentication as part of your resolver code or business logic code isn’t a good idea. It usually means that your app code knows too much about the authentication, and it’s probably coupled to it. It might to simpler to implement, but much more difficult to reuse later.

And, #2: You can implement authentication between your HTTP server and your GraphQL server, by using the GraphQL context. By implementing a custom context building function, you can access the network request and build your context object, and add currentUser to it. Then, your resolvers getting called by the GraphQL engine.

From our experience, we can tell that implementing authentication in this part of your GraphQL server gives you control over your authentication flow, if it’s done right.

In the next chapter we’ll dive in, and see how easy it’s to implement in with Apollo-Server.

Getting Started with Authentication

We’ll start by implementing a simple authentication flow in GraphQL server (using Apollo-Server 2.0).

I’m assuming that you already have your own favorite way to authenticate, whether it’s with headers, cookies or any other way. In the point of view of the server, we expect to get a token, and we’ll assume that it comes from the headers for the simplicity.

Let’s start by creating a simple Apollo-Server instance, with a very simple schema. We’ll focus on exposing the currentUser as a query field.

import { ApolloServer } from 'apollo-server'
import gql from 'graphql-tag'
 
const typeDefs = gql`
  type Query {
    me: User
  }
  type User {
    id: ID!
    username: String!
  }
`
 
const server = new ApolloServer({ typeDefs })
 
server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`)
})

Now, let’s implement context , and see if we can get a valid user out of the values we get there. Our goal in this part is only to validate the token, and try to trade it for a user. If the token or the user are not valid — we don’t want to throw an exception here, because that way, our server will not support public queries. So we’re just trying to get a user and a token, and return them to Apollo-Server, so it will use it as the context for our resolvers. If something went wrong with the validation — we just return null for the user and authToken.

auth-helpers.ts
export interface User {
  _id: string
  username: string
}
 
export async function tradeTokenForUser(token: string): Promise<User> {
  // Here, use the `token` argument, check its validity, and return
  // the user only if the token is valid.
  // You can also use external auth libraries, such as jsaccounts / passport, and
  // trigger its logic from here.
}
import { ApolloServer } from 'apollo-server'
import gql from 'graphql-tag'
import { tradeTokenForUser } from './auth-helpers'
 
const HEADER_NAME = 'authorization'
 
const typeDefs = gql`
  type Query {
    me: User
  }
  type User {
    id: ID!
    username: String!
  }
`
 
const server = new ApolloServer({
  typeDefs,
  context: async ({ req }) => {
    let authToken = null
    let currentUser = null
 
    try {
      authToken = req.headers[HEADER_NAME]
 
      if (authToken) {
        currentUser = await tradeTokenForUser(authToken)
      }
    } catch (e) {
      console.warn(`Unable to authenticate using auth token: ${authToken}`)
    }
 
    return {
      authToken,
      currentUser
    }
  }
})
 
server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`)
})

Note that in a good GraphQL server implementation, the context is built once per request, and shouldn’t change over time. So they only way to modify it is using the context function that builds it before the resolver getting called.

Now, we can finally implement the resolvers for the server:

import { ApolloServer } from 'apollo-server'
import gql from 'graphql-tag'
import { tradeTokenForUser } from './auth-helpers'
 
const HEADER_NAME = 'authorization'
 
const typeDefs = gql`
  type Query {
    me: User
  }
  type User {
    id: ID!
    username: String!
  }
`
 
const resolvers = {
  Query: {
    me: (root, args, context) => context.currentUser
  },
  User: {
    id: user => user._id,
    username: user => user.username
  }
}
 
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req }) => {
    let authToken = null
    let currentUser = null
 
    try {
      authToken = req.headers[HEADER_NAME]
 
      if (authToken) {
        currentUser = await tradeTokenForUser(authToken)
      }
    } catch (e) {
      console.warn(`Unable to authenticate using auth token: ${authToken}`)
    }
 
    return {
      authToken,
      currentUser
    }
  }
})
 
server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`)
})

This is a naive implementation, that assumes that the token is valid, because otherwise the context.currentUser will be null. That leads us to the next steps: implementing guards.

To add guards to your resolvers, you can use a simple Middleware approach, and wrap your resolver with a function that checks if context.user is valid, and otherwise, throws an error. You are getting full control over your authentication flow, because you can choose which resolvers to wrap.

So let’s implement a simple guard, and use it in our server. I also added a public part of the schema (Query.serverTime), that should be public, and not effected by the authentication flow. So it’s not wrapped with our guard.

authenticated-guard.ts
export const authenticated = next => (root, args, context, info) => {
  if (!context.currentUser) {
    throw new Error(`Unauthenticated!`)
  }
 
  return next(root, args, context, info)
}
import { ApolloServer } from 'apollo-server'
import gql from 'graphql-tag'
import { tradeTokenForUser } from './auth-helpers'
 
const HEADER_NAME = 'authorization'
 
const typeDefs = gql`
  type Query {
    me: User
    serverTime: String
  }
  type User {
    id: ID!
    username: String!
  }
`
 
const resolvers = {
  Query: {
    me: authenticated((root, args, context) => context.currentUser),
    serverTime: () => new Date()
  },
  User: {
    id: user => user._id,
    username: user => user.username
  }
}
 
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: async ({ req }) => {
    let authToken = null
    let currentUser = null
 
    try {
      authToken = req.headers[HEADER_NAME]
 
      if (authToken) {
        currentUser = await tradeTokenForUser(authToken)
      }
    } catch (e) {
      console.warn(`Unable to authenticate using auth token: ${authToken}`)
    }
 
    return {
      authToken,
      currentUser
    }
  }
})
 
server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`)
})

Great! So now our server is authenticated and we can get the currentUser in our resolvers. It also protects only the part of the GraphQL schema that we wanted to protect, and the authentication flow is separated from the resolvers, so each resolver does only what it needs.

Getting Started with Authorization

Now that we understand the concept of authentication in GraphQL servers, we can also implement authorization.

Implementing Authorization is no different from implementing the authenticated guard we did before. It’s just another guard, or resolver wrapper we can use.

Let’s add type Article to the schema, and allow creating article only to users with role set to EDITOR . It’s that simple:

import { ApolloServer } from 'apollo-server'
import gql from 'graphql-tag'
import { tradeTokenForUser } from './auth-helpers'
import { authenticated } from './authenticated-guard'
import { validateRole } from './validate-role'
 
const HEADER_NAME = 'authorization'
 
const typeDefs = gql`
  type Query {
    me: User
    serverTime: String
  }
  type User {
    id: ID!
    username: String!
  }
  type Article {
    id: ID!
    title: String!
    content: String!
  }
  type Mutation {
    publishArticle(title: String!, content: String!): Article!
  }
`
 
const resolvers = {
  Query: {
    me: authenticated((root, args, context) => context.currentUser),
    serverTime: () => new Date()
  },
  Mutation: {
    publishArticle: authenticated(
      validateRole('EDITOR')((root, { title, content }, context) =>
        createNewArticle(title, content, context.currentUser)
      )
    )
  },
  User: {
    id: user => user._id,
    username: user => user.username
  }
}
 
const server = new ApolloServer({
  typeDefs,
  resolvers,
  async context({ req }) {
    let authToken = null
    let currentUser = null
 
    try {
      authToken = req.headers[HEADER_NAME]
 
      if (authToken) {
        currentUser = await tradeTokenForUser(authToken)
      }
    } catch (e) {
      console.warn(`Unable to authenticate using auth token: ${authToken}`)
    }
 
    return {
      authToken,
      currentUser
    }
  }
})
 
server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`)
})
validate-role.ts
export const validateRole = role => next => (root, args, context, info) => {
  if (context.currentUser.role !== role) {
    throw new Error(`Unauthorized!`)
  }
 
  return next(root, args, context, info)
}

Next-Level Implementation with GraphQL-Modules

GraphQL Modules

If you wish to take your GraphQL server to the next level, and build a scalable, testable and readable server, I recommend to give GraphQL-Modules a try.

With GraphQL-Modules, you can separate your schema to smaller pieces, and creates modules that are in-charge of small parts of your code. It also provides a resolversComposition feature, that acts like a powerful middleware mechanism, that let you write your modules without knowing about the authentication flow, and just wrap your resolvers with guards in the app-level, so modules are independent and do only what they need, and the app that uses these modules decide which resolvers to authenticate.

It also super powerful, because you can implement your GraphQL-Module as standalone module, use it in multiple applications, and then apply different authentication/authorization rules in each app.

To implement the same example above with GraphQL-Modules, you can create a module called auth and move the context building logic to there. Then, create another module called common and the general parts, and articles to manage the articles features.

This Way — Each Module Is in Charge of a Different Feature of Our App

articles-module.ts
import { GraphQLModule } from '@graphql-modules/core'
 
export const articlesModule = new GraphQLModule({
  name: 'articles',
  // authModule must be imported, because this module uses currentUser in the context
  imports: [authModule],
  typeDefs: gql`
    type Article {
      id: ID!
      title: String!
      content: String!
    }
    type Mutation {
      publishArticle(title: String!, content: String!): Article!
    }
  `,
  resolvers: {
    Mutation: {
      publishArticle: (root, { title, content }, { currentUser }) =>
        createNewArticle(title, content, currentUser)
    }
  }
})
auth-module.ts
import { GraphQLModule } from '@graphql-modules/core'
import { tradeTokenForUser } from './auth-helpers'
 
const HEADER_NAME = 'authorization'
 
export const authModule = new GraphQLModule({
  name: 'auth',
  typeDefs: gql`
    type Query {
      me: User
    }
    type User {
      id: ID!
      username: String!
    }
  `,
  resolvers: {
    Query: {
      me: (root, args, { currentUser }) => currentUser
    },
    User: {
      id: user => user._id,
      username: user => user.username
    }
  },
  async contextBuilder({ req }) {
    let authToken = null
    let currentUser = null
 
    try {
      authToken = req.headers[HEADER_NAME]
 
      if (authToken) {
        currentUser = await tradeTokenForUser(authToken)
      }
    } catch {
      console.warn(`Unable to authenticate using auth token: ${authToken}`)
    }
 
    return {
      authToken,
      currentUser
    }
  }
})
common-module.ts
import { GraphQLModule } from '@graphql-modules/core'
 
export const commonModule = new GraphQLModule({
  name: 'common',
  typeDefs: gql`
    type Query {
      serverTime: String
    }
  `,
  resolvers: {
    Query: {
      serverTime: () => new Date()
    }
  }
})
resolvers-composition.ts
import { authenticated } from './authenticated-guard'
import { validateRole } from './validate-role'
 
export const resolversComposition = {
  'Query.me': [authenticated],
  'Mutation.publishArticle': [authenticated, validateRole]
}
server.ts
import { ApolloServer } from 'apollo-server'
import { resolversComposition } from './resolvers-composition'
 
const app = new GraphQLModule({
  name: 'app',
  imports: [commonModule, authModule, articleModule],
  resolversComposition
})
 
const { schema, context } = app
 
const server = new ApolloServer({
  schema,
  context
})
 
server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`)
})

The Future of Authentication with GraphQL-Modules and GraphQL @directives

If you wish to get rid of the resolversComposition wrapping, and use a more declarative way, you can use GraphQL directives to decorate your schema and put the authentication/authorization there.

To get started, add the following @directives to your auth module’s schema:

directive @auth on FIELD
directive @protect(role: String) on FIELD

Then, you can easily alter your resolversComposition into a function that accepts GraphQLSchema , and then check using your schema object, which resolves require which logic, and do a mapping:

import { getFieldsWithDirectives } from '@graphql-modules/utils'
import { authenticated } from './authenticated-guard'
import { validateRole } from './validate-role'
 
const DIRECTIVE_TO_GUARD = {
  auth: () => authenticated,
  protect: ({ role }) => validateRole(role)
}
 
export const resolversComposition = ({ typeDefs }) => {
  const fieldsAndTypeToDirectivesMap = getFieldsWithDirectives(typeDefs)
 
  for (const fieldPath in fieldsAndTypeToDirectivesMap) {
    const directives = fieldsAndTypeToDirectivesMap[fieldPath]
 
    if (directives.length > 0) {
      result[fieldPath] = directives
        .map(directive => {
          if (DIRECTIVE_TO_GUARD[directive.name]) {
            const mapperFn = DIRECTIVE_TO_GUARD[directive.name]
 
            return mapperFn(directive.args)
          }
 
          return null
        })
        .filter(Boolean)
    }
  }
 
  return result
}

The, you add the directives to your schema declaration:

type Mutation {
  publishArticle(title: String!, content: String!): Article! @auth @protect(role: "EDITOR")
}
 
type Query {
  me: User @auth
}

What’s Next?

Another thing we are working on, is having some of your repeated authentication and authorization logic installable through npm!

We are working with the creators of the js-accounts libraries to create a GraphQL Modules module out of their awesome packages, so you could just add it from npm and extend your server and schema.

You can read this blog post to learn to use this library with GraphQL-Modules in a few steps;

/blog/accountsjs-graphql-modules

Authentication and authorization is a fundamental part of your GraphQL server.

It should be easy and secure to implement and understand how to do that in order to help grow the GraphQL community

In this article we wanted to give you a complete overview of authentication and authorization in the GraphQL ecosystem and give you the tools and the knowledge to implement it on your GraphQL servers.

Please try all those different approaches and let us know your feedback about the best way for you and if we can improve it even further.

All Posts about GraphQL Modules

Join our newsletter

Want to hear from us when there's something new? Sign up and stay up to date!

By subscribing, you agree with Beehiiv’s Terms of Service and Privacy Policy.

Recent issues of our newsletter

Similar articles