Overview
In the last lesson, we learned that authentication is the process of determining whether a given user is logged in, and then determining which user someone is. In other words, authentication is checking that a user is who they say they are.
Airlock uses a simplified login process: users can log in as either a host account or a guest account. You can test out the login process at http://localhost:3000/login
(or on the deployed Airlock demo app).
In this lesson, we will:
- Learn how to authenticate users by using an HTTP header and the Apollo Server
context
object.
🕵️♂️ Authentication
There are many ways to provide credentials for authentication, such as HTTP headers, session cookies, or JSON web tokens.
Airlock uses HTTP headers. Let's take a closer look at how it works.
On the client side
Recall that GraphQL operations are sent from the client to the server in the form of an HTTP request. In Airlock, every operation includes an HTTP Authorization
header with a Bearer token to provide authentication credentials. The header looks something like this:
Authorization: Bearer user-1
Note: If you're curious, you can see the client code that adds this header in client/src/index.js
(where the authLink
variable gets set up).
Setting up client-side authentication is out of scope for this side quest, so we won't be going into this in detail. For more information, you can check out the Apollo Client docs on authentication.
In the example above, user-1
is the token that indicates which user is making the request. We can see that the token is simply "user-1", which is the exact ID of the user. In production environments, this token typically takes the shape of a temporary, encoded string, which would be generated by your user management system (like Auth0 or Okta). But for the purposes of this course, we'll stick with plain user IDs, for simplicity when testing.
On the server side
Once the GraphQL server receives the incoming client operation, it retrieves the token from the request header and attempts to authenticate the user. Let's take a look at how Airlock breaks down the steps in this process:
- The GraphQL server retrieves the Bearer token from the client request's
Authorization
header. - If a Bearer token exists, the server passes it to the
accounts
service, which tries to log in the corresponding user. - If the user login succeeds, the
accounts
service sends back an object with the user's profile data. Then the GraphQL server adds the user's id and role to thecontext
object that's available to every resolver.
From there, each resolver can use these user properties for authorization purposes, to determine what data the user has permission to access. (But we'll get to that in the next lesson!)
The code for this is in the server/index.js
file, in the context
property of the ApolloServer
initialization:
// ...context: async ({ req }) => {// 1) Retrieve the Bearer token from the request's Authorization header// (Note the lowercase "a" in authorization,// because all headers are transformed to lowercase)const token = req.headers.authorization || '';// Get the user token after "Bearer "const userId = token.split(' ')[1]; // e.g. "user-1"// Initialize the userInfo object where the user's id and role will be stored// with a successful authenticationlet userInfo = {};if (userId) {// 2) Authenticate the user using the accounts API endpointconst { data } = await axios.get(`http://localhost:4011/login/${userId}`).catch((error) => {throw AuthenticationError();});// 3) After a successful login, store the user's id and role// in the userInfo object,// which will be passed to `context` below for the resolvers to useuserInfo = { userId: data.id, userRole: data.role };}// for RESTDataSource classesconst { cache } = server;// Below is the `context` object resolvers will have access toreturn {...userInfo,dataSources: {bookingsDb: new BookingsDataSource(),reviewsDb: new ReviewsDataSource(),listingsAPI: new ListingsAPI({cache}),accountsAPI: new AccountsAPI({cache}),paymentsAPI: new PaymentsAPI({cache}),},};},// ...
Practice
Key takeaways
- One way clients can authenticate users is by passing an HTTP
Authorization
request header with the GraphQL operations it sends to the server. - Authentication logic can be written within the
context
property of theApolloServer
constructor, so that user information will be available to every resolver.
Up next
Now that we've seen how Airlock handles authentication, the next step is authorization. In the next lesson, we'll look at how Airlock uses the user info from the context
object to decide whether a user has permission to perform a certain operation.