To continue with our migration plan, we need to set up the router to run on the original server port (port
4000). Before we jump into the code to do that, let's take a look at how we're going to ensure that both the router and the monolith subgraph are set up to handle authentication and authorization correctly. In this lesson, we will:
- Review how auth works in Airlock's monolithic architecture
- Learn how to propagate authorization headers from the router to its subgraphs
How auth works in Airlock
Airlock has two types of users: hosts and guests. Each type of user can perform different kinds of actions.
When logged in as a host, you can:
- create listings
- manage bookings for your listings
- write reviews about guests
When logged in as a guest, you can:
- book places to stay
- write reviews about the location and the host
- manage your space credits
Currently, guest users are not allowed to create listings, and host users are not allowed to book places to stay.
With these business rules, our Airlock GraphQL API needs to control which users can see and interact with certain fields in the graph. This is where authentication and authorization come in.
- Authentication is determining whether a given user is logged in, and subsequently determining which user someone is. (They are who they say they are.)
- Authorization is determining what a given user has permission to do or see. (They're allowed to do what they're trying to do.)
Note: First time working with auth? Check out the Authentication & Authorization side quest for a closer look at how to verify user logins and permissions.
In Airlock, we're using HTTP headers, specifically the
Authorization header with a Bearer token to provide our user credentials. The header looks something like this:
Authorization: Bearer user-1
Once the GraphQL server receives the incoming client operation, it retrieves the token from the request header and attempts to authenticate the user using the accounts service. After a successful authentication, the accounts service returns an object with
userRole, which the GraphQL server then adds to the
contextValue object that's available to every resolver.
Airlock uses field-level authorization. That means each resolver checks whether the logged-in user has permission to access that part of the graph. They do so using the
userRole properties from the
Auth in a supergraph
In the original monolithic architecture, the GraphQL server received authorization headers from the client, did some logic to pull out the user information needed, then passed this information to its resolvers through the
In the supergraph architecture, the story remains similar. The router will receive authorization headers from the client, then pass this information to its subgraphs. Each subgraph will do the same logic to pull out the user information and pass it over to their resolvers.
How does the router pass this information to its subgraphs? And what does the subgraph need to do to pass it to its resolvers? Let's dive into these questions and answer them in the story of "Auth in a supergraph"!
Note: We're going to focus on the story where the app uses HTTP headers and field-level authorization. Some parts of the story may change if your application uses different auth methods.
Authenticating the user
The story starts in the same way our GraphQL query journey does: with a request from the client to the server. This particular request includes an
Authorization header that contains the logged-in user credentials.
The router receives this request and starts to create its query plan, using the supergraph schema as a reference to determine which subgraph to query for a particular field.
Next, the router executes the query plan. It starts at the top, sending a request to the subgraph. Included with this request is the
Authorization header that came from the client!
But how does the router know it should be passing along that header? It's all in the configuration!
Sending HTTP headers to subgraphs
When we start up the router, we have the option of passing in a configuration file, using the
--config flag. This allows us to customize the router in many ways, such as configuring CORS (which we saw in Voyage I), introspection, and HTTP headers.
Using the config file, we'll tell the router to send the
Authorization header to its subgraphs with every request. We'll see this configuration in action in the next lesson.
Over to the subgraph
When the subgraph receives the request from the router, it accesses the
Authorization header from the request and attempts to authenticate the user.
If the attempt is unsuccessful, the subgraph stops and sends an
AuthenticationError back to the router, which then sends it right back to the client.
If the authentication is successful, then the subgraph puts together an object containing user information and makes it available in its context. This user information object can be shaped in whatever way is going to be most useful to the subgraph's resolvers, and passed through the
The subgraph resolves the operations the same way as any other GraphQL server: they use their resolvers and data sources to retrieve and populate the requested data. And they can use the user information in the
contextValue object to check against the specific business rules that guard their field and who has access to it!
Back to the router
The subgraph sends back the requested data to the router, and the router continues with its query plan, eventually combining all those responses into a single JSON object.
Finally, the router sends the final JSON object back to the client. And that's the end of our operation's journey!
- In Airlock, the client sends its request to the router, with the addition of an HTTP header called
Authorization, which contains the current user's auth token.
- The router can be customized with a configuration file, to pass along HTTP headers to its subgraphs.
- The router passes the user's auth token to the subgraph, which checks whether or not the token is valid for login.
- If the login is unsuccessful, the subgraph throws an
AuthenticationErrorand sends it back to the router.
- If the login is successful, the subgraph adds the current user's information to its
contextValueobject, which is accessible by its resolvers for field-level authorization.
- If the login is unsuccessful, the subgraph throws an
We've learned how to handle auth in a supergraph, so let's put everything together and implement this in code!