Authentication in GraphQL with Apollo Server 4

Introduction to Authentication in GraphQL

In this lesson, we're diving into how to add authentication to your GraphQL server. Authentication is crucial for securing your API and ensuring that only authorized users can access specific resources. We'll be using Apollo Server, a popular GraphQL server, to implement this. While Apollo Server is widely used for its simplicity and active support, other alternatives include Express-GraphQL and Relay.

Our goal for this lesson is to:

  • Set up an Apollo Server with basic authentication.
  • Implement a login system.
  • Secure certain GraphQL mutations.

Setting Up the Apollo Server

First, let's quickly revise how we set up the GraphQL types and mock data. Our GraphQL server will have two mutations — one for logging in using the provided username and password and another for adding a new book given its author and title.

import { ApolloServer } from '@apollo/server';

const typeDefs = `#graphql
  type Book {
    id: ID!
    title: String!
    author: String!
  }

  type Query {
    books: [Book]
  }

  type Mutation {
    login(username: String!, password: String!): String
    addBook(title: String!, author: String!): Book
  }
`;

const users = [{ username: 'admin', password: 'admin' }];
const books = [
  { id: '1', title: 'The Hobbit', author: 'J.R.R. Tolkien' },
  { id: '2', title: 'Harry Potter', author: 'J.K. Rowling' }
];

Implementing Authentication Logic

Now let's implement the authentication logic to secure our GraphQL API.

For this example, we use a simple array to mock a user database.

const users = [{ username: 'admin', password: 'admin' }];

The login mutation takes a username and password and returns an authentication token if valid.

import { AuthenticationError } from '@apollo/server';

const resolvers = {
  Mutation: {
    login: (_, { username, password }) => {
      const user = users.find(user => user.username === username && user.password === password);
      if (!user) {
        throw new AuthenticationError('Invalid credentials');
      }
      return 'token';
    },
    addBook: (_, { title, author }, { token }) => {
      if (token !== 'Bearer ' + 'token') {
        throw new AuthenticationError('You must be logged in');
      }
      const newBook = { id: '3', title, author };
      books.push(newBook);
      return newBook;
    }
  }
};

Here, we check if the provided credentials match any user in our mock database. If they do, we return a token; otherwise, we throw an AuthenticationError.

Setting Up the Server

Finally, we start a GraphQL server, providing a proper authorization context on startup:

import { createServer } from 'http';
import { expressMiddleware } from '@apollo/server/express';
import express from 'express';
import bodyParser from 'body-parser';

const startApolloServer = async () => {
  const app = express();
  const httpServer = createServer(app);

  const server = new ApolloServer({
    typeDefs,
    resolvers
  });

  await server.start();

  app.use(
    '/graphql',
    bodyParser.json(),
    expressMiddleware(server, {
      context: async ({ req }) => {
        const token = req.headers.authorization || '';
        return { token };
      }
    })
  );

  httpServer.listen({ port: 4000 }, () => {
    console.log(`🚀 Server ready at http://localhost:4000/graphql`);
  });
};

startApolloServer();

Here we register a middleware for a /graphql route. bodyParser.json() parses the JSON body of incoming HTTP requests and makes it available in req.body. In the context of GraphQL, clients send their queries or mutations as JSON in the request body. bodyParser.json() ensures the server can read and process this JSON data. GraphQL requests typically include a query and variables in the request body. Without parsing, the server cannot interpret the body content. The context function runs for each incoming request, extracts the token from the Authorization header, and returns an object ({ token }), which becomes accessible to all resolvers during that request.

Testing the Implementation: Login

Testing the Implementation: Query Books

After we have authorized, let's query our books from the server:

async function queryBooks(token) {
  const query = `
    query {
      books {
        id
        title
        author
      }
    }
  `;

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
    body: JSON.stringify({ query }),
  });

  const data = await response.json();
  return data;
}

(async () => {
  const token = await login('admin', 'admin');
  console.log('Token:', token);
  
  const booksData = await queryBooks(token);
  console.log('Books:', JSON.stringify(booksData, null, 2));
})();

This function queries the books with the provided token.

Testing the Implementation: Adding a New Book

Overall Code Flow: How Authentication Works

Let's summarize the authorization flow in the Apollo Server with GraphQL:

  • Client Sends a Request: Client sends a GraphQL request with the query/mutation in JSON body and an Authorization header with the token if needed.
  • Middleware Execution:
    • bodyParser.json() processes the JSON body.
    • expressMiddleware extracts the token from the Authorization header and attaches it to the context.
  • Apollo Server Execution:
    • Validates the request against the schema.
    • Resolves fields using resolvers.
  • Resolver Authentication:
    • Uses the token from context to authenticate.
    • Executes the operation if valid, otherwise throws AuthenticationError.
  • Response to Client: Sends a JSON response with the data or an error message back to the client.

Lesson Summary

In this lesson, you learned how to add authentication to your GraphQL server using Apollo Server. We:

  • Set up the Apollo Server with basic authentication.
  • Implemented a login system to authenticate users.
  • Secured the addBook mutation to ensure only authenticated users can add books.

Next, you'll get hands-on practice with adding more secure queries and mutations. Great job on completing this lesson! Keep up the good work as you continue your journey in securing and optimizing GraphQL APIs.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal