apollo-client-msal-boilerplate
Boilerplate to get you started with Apollo Client authentication using MSAL
Posted on February 23, 2021
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
.
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.
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.
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:
Boilerplate to get you started with Apollo Client authentication using MSAL
Posted on February 23, 2021
Sign up to receive the latest update from our blog.