Apollo Client Authentication with MSAL
Tags: apollo, msal, authentication, react
Published: 2021-02-23 18:29:22

Managing authentication and the overhead that comes with it can be a hassle in terms of maintenance and security. That's why, it is easier to use an identity provider such as Google, Microsoft and others, to allow users to log into your app and navigate to protected routes.

In my company, we write frontends in React, and use Apollo Client for state management and to communicate with our GraphQL APIs, so we looked for a way to leverage MSAL (Microsoft Authentication Library) to acquire tokens from the Microsoft Identity Platform.

MSAL uses a refresh token to renew the access token that Apollo Client will send with requests. So it has a silent acquire mechanism to try to fetch access token using the cached refresh token, if it fails, it throws an exception, which means you will need user interaction with the Microsoft's login frame to fetch another access token once the user logs in again.

Without further ado, let's proceed with the code.
We are going to need the following packages in a react project.

`yarn add @apollo/client @azure/msal-browser @azure/msal-react`

src/
┣ app/
┃ ┣ hooks/
┃ ┃ ┗ useQueryUser.js
┃ ┣ services/
┃ ┃ ┣ Apollo/
┃ ┃ ┃ ┗ index.js
┃ ┃ ┗ Auth/
┃ ┃   ┣ auth-config.js
┃ ┃   ┗ index.js
┃ ┣ view/
┃ ┃ ┗ index.js
┃ ┗ index.js
┣ shared/
┃ ┗ helpers/
┃   ┗ Addhocs/
┃ ┃   ┗ index.js
┣ App.test.js
┣ index.css
┣ index.js
┗ setupTests.js

I will put a link for the github repo of the project, but for now let's take a look at the function that will acquire the token.

After instantiating MSAL and loading the configuration required, we can use hooks to call the library's functions.

First, the `AsyncTokenLookup` function will check if there are any cached users, if it finds one, it will try to acquire the token silently using `acquireTokenSilent` from MSAL.
If the process fails, we can prompt the user's interaction by initiating a redirect to Microsoft's login endpoint using `acquireTokenRedirect` or opening a popup using `acquireTokenPopup`.

```javascript
    const AsyncTokenLookup = async () => {
      const accounts = await instance.getAllAccounts();

      const account = get(accounts, "[0]");
      if (account && inProgress === "none") {
        try {
          const result = await instance.acquireTokenSilent({
            ...loginSilentRequest,
            account,
          });
          return result.accessToken;
        } catch (err) {
          if (err instanceof InteractionRequiredAuthError) {
            // fallback to interaction when silent call fails
            return instance.acquireTokenRedirect(loginRequest);
          }
        }
      } else if (!account && inProgress === "none") {
        return instance.acquireTokenRedirect(loginRequest);
      }
    };
```

Otherwise, if the silent acquiring succeeds, we return the access token to be sent by Apollo in the authorization header.

For that, we use the `setContext` function present in the `@apollo/client` package in order to inject the token in the Authorization header.

```javascript
    const withToken = setContext(async (_, { headers }) => {
      const token = await AsyncTokenLookup();
      return {
        headers: {
          ...headers,
          Authorization: token ? `Bearer ${token}` : null,
        },
      };
    });
```

Then, we will create a new Apollo client chaining httpLink and withToken.

```javascript
    const httpLink = createHttpLink({
      uri: process.env.REACT_APP_BACKEND_URI,
    });

    const client = new ApolloClient({
      link: from([withToken, httpLink]),
      cache: new InMemoryCache(),
    });
```

Note that in this example, we are only enabling accounts in one organizational directory only, and not personal Microsoft accounts. 

Here is a link to the github repo:
{% github https://github.com/othpwn/apollo-client-msal-boilerplate %}