How to Auth: Securing Your GraphQL API with Confidence
Tags: javascript, graphql, apollo
Published: 2020-08-14 15:20:33

*The following post is based on the code I demoed during my GraphQL Summit 2020 talk. [You can find the recording of that talk here](https://youtu.be/dBuU61ABEDs) and be sure to check out [the entire playlist of awesome talks here](https://www.youtube.com/playlist?list=PLpi1lPB6opQxkOv1H0j4JHhHx9bydEk8H).*

---

When building a GraphQL API, we often need to limit access to queries and mutations depending on who is requesting the data. The GraphQL spec doesn't provide any specific guidelines for how to manage "auth" with GraphQL, so it's up to us to choose our own adventure!

That said, it's a good idea to draw from battle-tested practices that have emerged over the years when it comes to layering _authentication_ (who a user is) and _authorization_ (what a user can do) onto a GraphQL API. In this post, I'll explore how we can use these best practices so we can lock down a GraphQL API with confidence.

## Starting Point

Before we jump into the code, it's important that we clarify a few assumptions we're going to make as we build out our API.

First, we're not going to lock down our entire GraphQL API endpoint. We will typically want to authorize user access to our API on a per-query or per-mutation basis. We may even want to manage access more granularly on a per-field basis. As a result, we'll need a more nuanced approach than protecting the entire API at the endpoint level.

Our next assumption is that we're going to use token-based authentication. Specifically, we'll use a [JSON Web Token](https://jwt.io/) (JWT), but you could use a similar approach with other kinds of tokens too.

Finally, we're going to use Express with Apollo Server because it will simplify the JWT handling and verification process with some ready-to-go middleware, but it's not explicitly necessary to use Express or any specific kind of Node middleware to do this (though highly recommended!).

## Installfest and Set-up

We'll begin by creating a directory for our project files:

```sh
mkdir basic-apollo-auth-demo && cd basic-apollo-auth-demo
```

Inside the new directory, we'll run `npm init --yes` to create a `package.json` file pre-populated with default values:

```sh
npm init --yes
```

Next, we'll install all of the dependencies we need for this project:

```sh
npm i apollo-server-express@2.15.1 esm@3.2.25 express@4.17.1 express-jwt@6.0.0 graphql-middleware@4.0.2 graphql-shield@7.3.2 jsonwebtoken@8.5.1 nodemon@2.0.4
```

Here's a quick summary of what we'll use each package for:

- `apollo-server-express`: To facilitate integrating Node.js middleware with our server, we'll use the Apollo/Express integration.
- `esm`: This package is a "babel-less, bundle-less ECMAScript module loader" that will allow us to use `import` and `export` in Node.js without any hassle.
- `express`: Again, we'll use Express to add some middleware to our server.
- `express-jwt`: This Express middleware will conveniently verify and decode an incoming JWT and add it to the Express `req` object for us.
- `graphql`: Apollo requires this library as a peer dependency.
- `graphql-middleware`: This package will allow us to wrap our schema so that we can execute code (i.e. permission checks!) before our resolver functions run.
- `graphql-shield`: GraphQL Shield will allow us to add an authorization layer to our GraphQL API as middleware.
- `jsonwebtoken`: We'll use this package to create and sign a JWT when a user logs in.
- `nodemon:` Nodemon will automatically reload our application when files change in the project directory.

We'll also add a directory to organize our project and create a few files in it too:

```sh
mkdir src && touch src/index.js src/typeDefs.js src/resolvers.js src/data.js
```

Let's start with `src/data.js`. Rather than using a database we'll work with mocked data in our resolvers, so we'll need to add that data to this file:

```js
export const users = [
  {
    id: "12345",
    name: "Gene Kranz",
    email: "gene@nasa.gov",
    password: "password123!",
    roles: ["director"],
    permissions: ["read:any_user", "read:own_user"]
  },
  {
    id: "67890",
    name: "Neil Armstrong",
    email: "neil@nasa.gov",
    password: "password890!",
    roles: ["astronaut"],
    permissions: ["read:own_user"]
  }
];
```

Next, we'll add an object type called `User` with a corresponding query to fetch a single user by their ID in `src/typeDefs.js`:

```js
import { gql } from "apollo-server-express";

export default gql`
  type User {
    id: ID!
    name: String
  }

  type Query {
    user(id: ID!): User
  }
`;
```

We'll also need to add a resolver for the `user` query to `src/resolvers.js`:

```js
import { users } from "./data";

export default {
  Query: {
    user(parent, { id }) {
      return users.find(user => user.id === id);
    }
  }
};
```

In our `src/index.js` file, we can now set up Apollo Server with Express. We set up our `ApolloServer` as usual, passing in the imported `typeDefs` and `resolvers`, and then we integrate Express with Apollo Server by calling the `applyMiddleware` method on the new `ApolloServer` instance and pass in the top-level Express `app`:

```js
import { ApolloServer } from "apollo-server-express";
import express from "express";

import resolvers from "./resolvers";
import typeDefs from "./typeDefs";

const port = 4000;
const app = express();

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

server.applyMiddleware({ app });

app.listen({ port }, () => {
  console.log(`Server ready at http://localhost:${port}${server.graphqlPath}`);
});
```

Lastly, we'll add a script to our `package.json` file that will allow us to start up our GraphQL API:

```json
{
  // ...
  "scripts": {
    "server": "nodemon -r esm ./src/index.js"
  },
  // ...
}
```

Now we can run `npm run server` and we should be able to test our API in GraphQL Playground at [http://localhost:4000/graphql](http://localhost:4000/graphql). Try running a `user` query to get one of the users by their ID to make sure that it works before moving on to the next section.

## Make Incoming JWT Available to Resolvers

As previously mentioned, we're going to use JWTs to help protect our API. Specifically, we will require a valid JWT to be sent in the `Authorization` header of every request. JWTs conform to an open standard that describes how information may be transmitted as a compact JSON object and they consist of three distinct parts:

1. **Header:** Contains information about the token type and the algorithm used to sign the token (for example, HS256).
2. **Payload:** Contains claims about a particular entity. These statements may have predefined meanings in the JWT specification (known as *registered* claims) or they can be defined by the JWT user (known as *public* or *private* claims).
3. **Signature:** Helps to verify that no information was changed during the token’s transmission by hashing together the token header, its payload, and a secret.

A typical JWT will look something like this:

```text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL3NwYWNlYXBpLmNvbS9ncmFwaHFsIjp7InJvbGVzIjpbImFzdHJvbmF1dCJdLCJwZXJtaXNzaW9ucyI6WyJyZWFkOm93bl91c2VyIl19LCJpYXQiOjE1OTQyNTI2NjMsImV4cCI6MTU5NDMzOTA2Mywic3ViIjoiNjc4OTAifQ.Z1JPE53ca1JaxwDTlnofa3hwpS0PGdRLUMIrC7M3FCI
```

Even though the JWT above may look encrypted, it has only been base64url-encoded to make it as compact as possible. That means that all of the information inside can just as easily be decoded again. Similarly, the signature portion of the JWT only helps us ensure that data hasn't been changed during its transmission, so it’s important to not put any secret information inside the JWT header or payload in cleartext.

The header section of the above token would decode to:

```json
{
  "alg": "HS256",
  "typ": "JWT"
}
```

And the payload section would decode as follows:

```json
{
  "https://spaceapi.com/graphql": {
    "roles": ["astronaut"],
    "permissions": ["read:own_user"]
  },
  "iat": 1594252663,
  "exp": 1594339063,
  "sub": "67890"
}
```

In the token’s payload, the `sub`, `iat`, and `exp` claims represent *registered* claims. The `sub` claim (short for “subject”) is a unique identifier for the object described by the token. The `iat` claim is the time at which the token was issued. The `exp` claim is the time that the token expires. These claims are a part of the JWT specification.

The claim with the `https://spaceapi.com/graphql` key is a user-defined claim added to the JWT. Custom public claims included in a JWT must be listed in the [IANA JSON Web Token Registry](https://www.iana.org/assignments/jwt/jwt.xhtml) or be defined with a collision-resistant namespace such as a URI, as was done above.

You can experiment with encoding and decoding JWTs at [https://jwt.io](https://jwt.io/).

At this point, you may be wondering how we'd use a JWT during the authentication process and how we can use the data contained within to authorize a user to access various features of our API. At a high level, when a user logs in—with their username and password in our case—the server will verify their credentials against the data saved in the database and then create a signed JWT to send back to the client. 

The user can then send this token back to the server with every subsequent request (until the JWT expires) so the server can verify the JWT and respond with the protected data if the JWT is valid. In the example that follows, we'll send the JWT to the server in the `Authorization` header of each request.

To simplify the JWT-handing process, we'll use the express-jwt package we previously installed to add middleware to Express that will intercept an incoming JWT, verify and decode it, and then add the decoded token to the `req` object as a `user` property.

Let's add the middleware in `src/index.js` now:

```js
import { ApolloServer } from "apollo-server-express";
import express from "express";
import expressJwt from "express-jwt"; // NEW!

// ...

app.use(
  expressJwt({
    secret: "SUPER_SECRET",
    algorithms: ["HS256"],
    credentialsRequired: false
  })
); // NEW!

// ...
```

Above, we've called the `expressJwt` function and pass in a secret string to sign the JWT. For demonstration purposes only, the secret has been added directly to this file but you would likely want to keep track of this value in an environment variable instead.

We also specify the signing algorithm to be HS256. HS256 is a symmetric signing algorithm so we'll need to use the same secret when verifying it and when we later create a JWT when the user signs in.

Lastly, we set the `credentialsRequired` option to `false` so Express won’t throw an error if a JWT hasn’t been included, which would be the case when a user initially logs in or when GraphQL Playground polls for schema updates.

And if you're wondering what kind of middleware magic happens under the hood here, express-jwt will get the token from the `Authorization` header of an incoming request, decode it, and add it to the `req` object as the `user` property.

Next, we can use the Apollo Server's `context` option to access the decoded token from the `req` object and pass this data down the graph to our resolvers. It’s a common practice to add decoded tokens to Apollo Server’s `context` because this object is conveniently available in every resolver and it’s recreated with every request so we won’t have to worry about tokens going stale.

In `src/index.js`, we'll check for the `user` object in the request and add it to the Apollo Server `context` if it exists, otherwise we just set the `user` to `null` because we don't want to error out here if a token isn't available:

```js
// ...

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    const user = req.user || null;
    return { user };
  } // UPDATED!
});

// ...
```

With this code in place, if a JWT accompanies any request to our API, then we'll be able to access the decoded object from the `context` parameter of any resolver function.

## Add a Login Mutation

Now that we can handle an incoming token, we need a way to create one in the first place when a user wants to log in. And this leads us to a very big question—should authentication be handled within the GraphQL server, or is this out of band?

Some people say we should leave authentication entirely out of the schema. In other words, we shouldn't have login or logout mutations. That would mean we just have the decoded token data available in the resolvers' `context` parameter and leave it at that.

I would say, in practice, there's a very high likelihood that you would want to use some kind of dedicated auth service (and perhaps even use something like Auth0) to manage your app's authentication needs. However, to keep things manageable for the scope of this tutorial, we'll implement a `login` mutation so we can get a sense of how JWT would be created.

To do this, we'll need to update `src/typeDef.js`:

```js
import { gql } from "apollo-server-express";

export default gql`
  # ...

  type Mutation {
    login(email: String!, password: String!): String
  } # NEW!
`;
```

And over in `src/resolvers.js`, we'll add a `login` resolver that finds the user in our database whose email and password match the incoming arguments, and then we'll use the `jsonwebtoken` package to create and sign a JWT for them:

```js
import jwt from "jsonwebtoken";

import { users } from "./data";

export default {
  // ...
  Mutation: {
    login(parent, { email, password }) {
      const { id, permissions, roles } = users.find(
        user => user.email === email && user.password === password
      );
      return jwt.sign(
        { "https://spaceapi.com/graphql": { roles, permissions } },
        "SUPER_SECRET",
        { algorithm: "HS256", subject: id, expiresIn: "1d" }
      );
    }
  } // NEW!
};
```

The first argument we pass into the `sign` method above is an object containing the JWT information we want to add to the payload of the token. And because we're adding some custom info to this token, we namespace it using the URL of the GraphQL API as property with the user's permissions and roles as a value.

As a second option, we pass in the same secret that we used to verify the token before. And as a third option, we can pass in additional options such as the unique `subject` value (which is the user's ID), a token expiration time, and the signing algorithm we want to use.

## Add a Viewer Query

We have one final step to complete before we can test out our updated code in GraphQL Playground. We're going to add a `viewer` query that will return the authenticated user based on the token included in the `Authorization` header of the request.

We'll updated our code in `src/typeDefs.js`:

```js
import { gql } from "apollo-server-express";

export default gql`
  # ...

  type Query {
    user(id: ID!): User
    viewer: User! # NEW!
  }

  # ...
`;
```

As a sidebar here, it's a good practice to expose a `viewer` query that acts as the entry point for what an authenticated user can do with an API. If we were to fully realize that in our API, we could add a `Viewer` object type to use as the return type for the `viewer` query and expose fields on that type that allow an authenticated user to query relevant data. I encourage you to take a look at the [GitHub GraphQL API](https://docs.github.com/en/graphql) for a working implementation of this.

We'll also need to add the corresponding resolver in `src/resolvers.js`:

```js
import jwt from "jsonwebtoken";

import { users } from "./data";

export default {
  Query: {
    // ...
    viewer(parent, args, { user }) {
      return users.find(({ id }) => id === user.sub);
    } // NEW!
  },
  // ...
};
```

In the code above, we get the currently authenticated user's information by using their ID value, which is available in the `sub` claim of the decoded token in the `context` object parameter.

We're now ready to try out our API again in GraphQL playground. Let's try running a `login` mutation first:

```graphql
mutation {
  login(email: "neil@nasa.gov", password: "password890!")
}
```

The `login` mutation will return a JWT like this:

```json
{
  "data": {
    "login": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL3NwYWNlYXBpLmNvbS9ncmFwaHFsIjp7InJvbGVzIjpbImFzdHJvbmF1dCJdLCJwZXJtaXNzaW9ucyI6WyJyZWFkOm93bl91c2VyIl19LCJpYXQiOjE1OTU3MDA2ODUsImV4cCI6MTU5NTc4NzA4NSwic3ViIjoiNjc4OTAifQ.l4Afg9-suWBROzN7xU1qkZENgMWcy1msoekm8roSqsI"
  }
}
```

We can then copy that JWT and add it to the "HTTP Headers" panel of GraphQL Playground in this format:

```json
{
  "Authorization": "Bearer "
}
```

Now we can try running the `viewer` query with this header added:

```graphql
query {
  viewer {
    name
  }
}
```

And we'll see that we get back information about the authenticated user, as expected:

```json
{
  "data": {
    "viewer": {
      "id": "67890",
      "name": "Neil Armstrong"
    }
  }
}
```

## Add Authorization by Checking Permissions

Before we can finish building our GraphQL API, we need to understand a few things about authorization. While we now have a way to identify users based on tokens in place, we still don’t have any mechanism to limit API access to authenticated users This is where authorization comes in!

The most basic level of authorization is letting users run queries based on whether they are authenticated, and we're going to do this, but we'll also add finer-grained authorization to our queries based on the permissions in the logged-in user's JWT.

When adding authorization to GraphQL API, we have a few different options available. We could directly check the authenticated user’s ID and permissions inside of each resolver, but this wouldn't be very DRY, so let's just count that one as off the table.

Instead, one popular option for adding authorization involves adding [custom schema directives](https://www.apollographql.com/docs/apollo-server/security/authentication/#authorization-via-custom-directives) to control access to various types and fields. Alternatively, we could use a package like [GraphQL Auth](https://www.npmjs.com/package/graphql-auth) to wrap our resolver functions explicitly with permission checks. Similarly, we could use a package like [GraphQL Shield](https://github.com/maticzav/graphql-shield) to completely abstract the authorization rules into a middleware layer.

For our API, we'll choose GraphQL Shield. First, we'll need to add a `permissions.js` file to our project:

```sh
touch src/permissions.js
```

Inside of `src/permissions.js`, we'll first create a little helper function that we'll use to check if a decoded user token has a permission applied:

```js
function checkPermission(user, permission) {
  if (user && user["https://spaceapi.com/graphql"]) {
    return user["https://spaceapi.com/graphql"].permissions.includes(
      permission
    );
  }
  return false;
}
```

Next, we'll import a few things into this file from GraphQL Shield that will help us apply authorization rules to our schema. First, we'll focus on the `rule` function, which has all of the same parameters as a typical resolver function, including the `context`.

We use the `rule` function to, not surprisingly, create an authorization rule. The first one we create will check if a user is authenticated by verifying that the decoded JWT is present in the `context`:

```js
import { and, or, rule, shield } from "graphql-shield"; // NEW!

function checkPermission(user, permission) {
  if (user && user["https://spaceapi.com/graphql"]) {
    return user["https://spaceapi.com/graphql"].permissions.includes(
      permission
    );
  }
  return false;
}

const isAuthenticated = rule()((parent, args, { user }) => {
  return user !== null;
}); // NEW!

```

Note that if we return `false` from any rule, then authorization will be denied.

Now we can add some more complex rules to `src/permissions.js` that check what permissions have been assigned to a user:

```js
// ...

const isAuthenticated = rule()((parent, args, { user }) => {
  return user !== null;
});

const canReadAnyUser = rule()((parent, args, { user }) => {
  return checkPermission(user, "read:any_user");
});

const canReadOwnUser = rule()((parent, args, { user }) => {
  return checkPermission(user, "read:own_user");
});

const isReadingOwnUser = rule()((parent, { id }, { user }) => {
  return user && user.sub === id;
});
```


The `canReadAnyUser` and `canReadOwnUser` rules each check for the corresponding permissions in the JWT and return `false` if they don't exist, and the `isReadingOwnUser` rule verifies that the ID of the user requested in the query matches the ID of the authenticated user.

A final step in `src/permissions.js`, we'll call the `shield` function and pass it an object whose shape mirrors our resolvers. Inside of this object, we'll use our newly created rules to describe how to check authorization for each query:

```js
// ...

export default shield({
  Query: {
    user: or(and(canReadOwnUser, isReadingOwnUser), canReadAnyUser),
    viewer: isAuthenticated
  }
});
```

For the `viewer` query, we only require that a user is authenticated to run the query. For the `user` query, we employ the logical `and` and `or` functions provided by GraphQL Shield to check a more complex configuration of rules. For this case, we allow users to query for a user if they are requesting their user and have the `read:own_user` permission assigned to them. Alternatively, they can view any user if they have the `read:any_user` permission assigned.

## Add Permissions as GraphQL Middleware

We're nearly done, but we have to make some updates to `src/index.js` to add the permissions as a middleware to the GraphQL API. We can do this using the GraphQL Middleware package and importing our `permissions` into this file as well:

```js
import { ApolloServer, makeExecutableSchema } from "apollo-server-express"; // UPDATED!
import { applyMiddleware } from "graphql-middleware"; // NEW!
import express from "express";
import expressJwt from "express-jwt";

import permissions from "./permissions"; // NEW!
import resolvers from "./resolvers";
import typeDefs from "./typeDefs";

// ...
```

We'll also need to update our `ApolloServer` config to accept a `schema` with the middleware applied instead of directly taking the `typeDefs` and `resolvers` as options:

```js
// ...

const server = new ApolloServer({
  schema: applyMiddleware(
    makeExecutableSchema({ typeDefs, resolvers }),
    permissions
  ), // UPDATED!
  context: ({ req }) => {
    const user = req.user || null;
    return { user };
  }
});

// ...
```

Our secured API is now ready to go! Let's head back over to GraphQL Playground to try it out. First, let's run the `user` query using the same `Authorization` header as before (which we obtained for the non-director user), but we'll try to retrieve information about the other user instead:

```graphql
query {
  user(id: "12345") {
    name
  }
}
```

We'll see that we get back a "Not Authorised!" message instead of the user's data. However, we can rerun the query using the authenticated user's ID and we'll see that we get back a successful response:

```graphql
query {
  user(id: "67890") {
    name
  }
}
```

You can try logging in and obtaining a token for the user with the director role as well now. If you use that token in the HTTP Headers panel when making a `user` query, then you'll be able to query for either user because you'll have the `read:any_user` permission available.

## Summary

In this post, we went on a whirlwind tour of how authentication and authorization can be handled with a GraphQL API using Express and Apollo Server.

Specifically, we saw how we can handle incoming JWTs in an `Authorization` header and pass that decoded data down the graph to resolvers. We also saw how a `viewer` query can act as an entry point for authenticated users to the API and how we can keep authorization checks out of resolvers functions by abstracting them into a middleware layer.

You can also find the [complete code for this tutorial on GitHub](https://github.com/mandiwise/basic-apollo-auth-demo). 

If you enjoyed this post, I've also written a blog post about [how to handle authentication and authorization with Apollo Federation](https://www.apollographql.com/blog/setting-up-authentication-and-authorization-with-apollo-federation/) on the official Apollo blog, as well as [a post on using passwordless authentication with GraphQL](https://dev.to/mandiwise/passwordless-authentication-for-graphql-apis-with-magic-2chk), and you can read more about building full-stack JavaScript applications in my book [Advanced GraphQL with Apollo & React](https://8bit.press/book/advanced-graphql).