Overview
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 listingif (!userId) throw AuthenticationError();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 errors defined in the utils/errors.js
file. For more information on how to create custom errors and codes, 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
.
Practice
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 GraphOS Studio to test out our auth setup.