3. Authorization: Checking permissions


In the previous lesson, we saw how to verify a user's identity by signing them in to Airlock. But there's still more work to do!

We know that Airlock has two different types of users: guests and hosts. And each type of user is only allowed to perform certain tasks. (For example, hosts can manage location listings, but guests cannot.)

Now that we've learned how to authenticate users, the next step is authorization: checking that the logged in user is allowed to perform whatever action they're trying to do.

In this lesson, we will:

  • Learn how to use context to grant field access for authorized users at the resolver level.

🔐 Authorization

As with authentication, there are multiple ways to handle authorization. First, there's the permission levels: you could restrict access to the entire API, to individual data sources, or to individual fields. Then there's the implementation: you could define custom directives, or even let services outside of the GraphQL server take care of authorization! (You can learn more about each approach in the Apollo Server authorization docs.)

For Airlock, we're using field-level authorization. That means each resolver checks whether the logged-in user has permission to access that part of the graph.

Let's take a closer look at authorization in Airlock by exploring the resolver for one particular mutation: createListing.

Inside the createListing resolver

As mentioned before, Airlock hosts can create listings for places they want to rent out to guests. When a host wants to create a new listing, the Airlock client calls the createListing mutation.

Only hosts can create listings, so the createListing mutation needs to have authorization guards in place to prevent guests from performing that action.

The resolver for createListing is in the server/resolvers.js file. Recall that a resolver is a function with four optional parameters: parent, args, context, and info. The third parameter, context, is where we'll find the userId and userRole properties that we set in the previous lesson.

In the resolvers.js file, search for the createListing mutation. It'll look something like this:

createListing: async (_, {listing}, {dataSources, userId, userRole}) => {
// the user needs to be logged in to create a listing
if (!userId) throw new AuthenticationError(authErrMessage);
if (userRole === 'Host') {
// hosts can create listings
} else {
// throw a ForbiddenError

First, the resolver checks whether there's an existing userId from the context object, because a user has to be logged in to create a listing. If there isn't a logged-in user, then the resolver throws an AuthenticationError.

Next, the resolver checks whether the userRole is a Host before it executes the logic to create the listing. If the user is not a host, then the resolver returns an error.

Note: AuthenticationError and ForbiddenError are both error subclasses defined by Apollo Server. For more information on these and other kinds of errors, check out the Apollo Docs on error handling.

You can use a similar structure in other resolvers to make sure that only users with a certain role can access certain fields or operations. Want some more examples? Check out the resolvers for upcomingGuestBookings or updateProfile.


To implement field-level authorization, where can we define logic to ensure that only certain users can access certain parts of the graph?

Key takeaways

  • With field-level authorization, each resolver determines whether the logged-in user has permission to access a field, query, or mutation in the schema.

Up next

In the next lesson, we'll take a step back and see how to use Apollo Studio to test out our auth setup.