Authenticate users

Access control is a feature that almost every if not every app will have to handle at some point. In this tutorial, we're going to focus on the essential concepts of authenticating users instead of focusing on a specific implementation. This will enable you understand what authenticating a user is, then you can now freely choose any approach when working on your projects.

Here are the steps you'll want to follow:

  1. The context function on your ApolloServer instance is called with the request object each time a GraphQL operation hits your API. Use this request object to read the authorization headers.
  2. Authenticate the user within the context function.
  3. Once the user is authenticated, attach the user to the object returned from the context function. This allows us to read the user's information from within our data sources and resolvers, so we can authorize whether they can access the data.

With the above flows at hand, let's get started. Open up src/index.js and update the context function on ApolloServer to the code shown below:

// src/index.js
const isEmail = require('isemail');
const server = new ApolloServer({
  context: async ({req}) => {    
    const auth = req.headers && req.headers.authorization || '';
    const email = Buffer.from(auth, 'base64').toString('ascii');
    if (!isEmail.validate(email)) return { user: null };
    const users = await store.users.findOrCreate({ where: { email } });
    const user = users && users[0] || null;
    return { user: { ...user.dataValues } };

Just like in the steps outlined above, we're checking the authorization headers on the request, authenticating the user by looking up their credentials in the database, and attaching the user to the context. While we definitely don't advocate using this specific implementation in production since it's not secure, all of the concepts outlined here are transferable to how you'll implement authentication in a real-world application.

You may ask, how do we create the token passed to the authorization headers? You asked correctly, In the next paragraph we will write our resolver for the login mutation.

Writing Mutation resolvers

Writing Mutation resolvers is similar to the resolvers we've already written. First, let's write the login resolver to complete our authentication flow. Add the code below to your resolver map underneath the Query resolvers:

Mutation: {
  login: async (_, { email }, { dataSources }) => {
    const user = await dataSources.userAPI.findOrCreateUser({ email });
    if (user) return Buffer.from(email).toString('base64');

The login resolver receives an email address and returns a token if a user exists. In a later section, we'll learn how to save that token on the client.

Now, let's add the resolvers for bookTrips and cancelTrip to Mutation:

// src/resolvers.js
Mutation: {
  bookTrips: async (_, { launchIds }, { dataSources }) => {
    const results = await dataSources.userAPI.bookTrips({ launchIds });
    const launches = await dataSources.launchAPI.getLaunchesByIds({

    return {
      success: results && results.length === launchIds.length,
        results.length === launchIds.length
          ? 'trips booked successfully'
          : `the following launches couldn't be booked: ${launchIds.filter(
              id => !results.includes(id),
  cancelTrip: async (_, { launchId }, { dataSources }) => {
    const result = await dataSources.userAPI.cancelTrip({ launchId });
    if (!result)
      return {
        success: false,
        message: 'failed to cancel trip',
    const launch = await dataSources.launchAPI.getLaunchById({ launchId });
    return {
      success: true,
      message: 'trip cancelled',
      launches: [launch],

Both bookTrips and cancelTrip must return the properties specified on our TripUpdateResponse type from our schema, which contains a success indicator, a status message, and an array of launches that we've either booked or cancelled. The bookTrips mutation can get tricky because we have to account for a partial success where some launches could be booked and some could fail. Right now, we're simply indicating a partial success in the message field to keep it simple.

Previous: Build a paginated list
Next: Update local data with resolvers