Back

Authentication in React with Microsoft's MSAL library

Authentication in React with Microsoft's MSAL library

Authentication is mandatory in today’s apps, and Microsoft’s Authentication Library (MSAL) is a strong solution to this problem. This article will show you all the details about implementing MSAL in your own React code.

In today’s digital landscape, ensuring the security and privacy of user data is paramount. Whether you’re building a web application, a mobile app, or any other software that requires user authentication, Microsoft’s Authentication Library for React (MSAL-React) offers a robust solution to streamline the process. With MSAL-React, developers can integrate secure authentication seamlessly into their applications, providing users a safe and user-friendly experience. In this comprehensive guide, we’ll take you step by step through the process of implementing authentication with MSAL-React, helping you leverage the power of Microsoft’s identity platform to safeguard your users’ data and enhance the trustworthiness of your application. Whether you’re a seasoned developer or just starting your journey, this article will equip you with the knowledge and tools to incorporate robust authentication in your React applications.

Setting Up the Development Environment

The necessary software and tools required to build the app are:

  • Node.js and npm: Install Node.js, which includes npm from the official website. npm would be used to manage dependencies and run scripts.
  • Duplicating a template: This template already has the single page front end built. Here’s how to duplicate it:
    • First, fork the repository.
    • Open your terminal and clone the repository by running this command in your terminal.
git clone <your_repository_url> 

Navigate to the project directory using this command in the terminal.

cd msal-react-demo-template

After navigating to the app directory, install the dependencies.

npm install

Start the app.

npm start

Here’s a first look at the app’s user interface. add

  • Installing necessary dependencies: Install msal-react and msal-browser by using the following command on the terminal.
npm install --save @azure/msal-browser @azure/msal-react

Below is the project structure:

msal-react-demo-template/
|-- node_modules/
|-- public/
|-- src/
|   |-- components/
       |-- NavBar.jsx
       |-- PageLayout.jsx
       |-- ProfileData.jsx
       |-- SignInButton.jsx
       |-- SignOutButton.jsx
       |-- WelcomeName.jsx
|   |-- pages/
       |-- Home.jsx
       |-- Profile.jsx
|   |-- styles/
       |-- theme.js
|   |-- App.js
|   |-- index.js
|-- .gitignore
|-- LICENSE
|-- package-lock.json
|-- package.json
|-- README.md

Azure AD App Registration

Azure Active Directory (Azure AD) is Microsoft’s cloud-based identity and access management service. It provides a comprehensive solution for managing user identities and securing access to applications and resources in the cloud and on-premises. In the context of this guide, Azure AD is crucial for adding authentication and authorization to your application, ensuring that only authorized users can access your resources.

Creating an Azure AD App Registration and Obtaining Client ID

  • Go to https://portal.azure.com/.
  • Sign in using your Microsoft account or create one.
  • In the search bar, search for “App Registration”.
  • Click on “New Registration”.
  • Fill in the name you want to use for the app.
  • Choose the supported account type. For this article, we will be using Personal Microsoft Accounts only.
  • For the Redirect URI, select “Single-page application (SPA)” and set the URI to http://localhost:3000/.
  • Click on “Register”.
  • On the Overview menu, you can copy the client ID.

MSAL-React Integration

Configuring MSAL-React within your application to enable secure and user-friendly Azure AD-based authentication and authorization.

Setting up the configuration for MSAL

In the index.js file, you’ll configure the integration of MSAL-React into the React application by following these steps.

  1. Import the necessary library: First, import the PublicClientApplication from msal-browser. MSAL (Microsoft Authentication Library) is a library that facilitates Azure AD authentication and authorization.
import { PublicClientApplication } from '@azure/msal-browser';
  1. Instantiate the pubClientApp object and provide configuration options: Create a pubClientApp object by configuring it with essential options. These options define how your application interacts with Azure AD. Here’s an explanation of each option:
  • clientId: This is your application’s client ID obtained from the Azure AD application registration.
  • authority: The authority URL specifies where the authentication and authorization should occur. For Azure AD consumer accounts, use ’https://login.microsoftonline.com/consumers‘.
  • redirectURI: This is the URI to which the user will be redirected after successful authentication. You should configure it to match your application’s settings.
const pubClientApp = new PublicClientApplication({
  auth: {
    clientId: "Paste your client ID",
    authority: "https://login.microsoftonline.com/consumers",
    redirectUri: "/",
  },
});

Note: To ensure optimal performance, it’s crucial to instantiate the pubClientApp object outside the component tree. This prevents it from being recreated whenever a component re-renders, which can be inefficient. By placing it outside, you guarantee it’s created only once and reused as needed.

  1. Pass the pubClientApp object as a prop to your application component: Now, make the pubClientApp object available to the application component. This is typically done by passing it as a prop to the component, which allows the application to manage authentication and authorization seamlessly.
<App msalInstance={pubClientApp}/>

Initializing MSAL Provider

To enable components within your application to access the authentication state, you should wrap them in the MsalProvider component. Follow these steps to set it up in the App.js file:

  • First, import the MsalProvider component from the msal-react library.
import { MsalProvider } from "@azure/msal-react";
  • Wrap the App components with MsalProvider. Provide the msalInstance prop and pass the application instance that was configured to MsalProvider.
function App({ msalInstance }) {
  return (
    <MsalProvider instance={msalInstance}>
      <PageLayout>
        <Grid container justifyContent="center">
          <Pages />
        </Grid>
      </PageLayout>
    </MsalProvider>
  );
}

By wrapping the components with MsalProvider, the application can access the msal-react context. This context provides access to authentication-related functionality, making implementing secure authentication and authorization within your React application easier.

Creating a Sign-In Component

To create a Sign-In component for the application, follow these steps in the SignInButton.jsx file:

  • Begin by importing the useMsal hook to access the MSAL instance.
import { useMsal } from '@azure/msal-react';
  • Utilize the useMsal hook to access the MSAL instance. Create a variable named instance using the hook, enabling access to the previously configured MSAL instance.
const { instance } = useMsal();
  • Define a’ handleLogin’ function with the instance.loginPopup() method. This function prompts users with a pop-up to sign in using their username and password
const handleLogin = () => {
  instance.loginPopup();
};
  • Customize the sign-in experience by specifying options like scopes to request permissions when users sign in for the first time.
const handleLogin = () => {
  instance.loginPopup({
    scopes: ["user.read"],
  });
};
  • Add the onClick attribute that invokes the handleLogin function to the button in the component.
<Button color="inherit" onClick={handleLogin}>
      Sign in
</Button>

Here’s the SignInButton.jsx file for reference:

import Button from "@mui/material/Button"; // Importing a button component from the Material-UI library.
import { useMsal } from "@azure/msal-react"; // Importing the useMsal hook from Azure MSAL for handling authentication.

export const SignInButton = () => {
  const { instance } = useMsal(); // Access the instance object from the useMsal hook.

  const handleLogin = () => {
    instance.loginPopup({
      scopes: ["user.read"], // Configuring the loginPopup with the "user.read" scope.
    });
  };

  return (
    <Button color="inherit" onClick={handleLogin}>
      Sign in {/* Render a button with the label "Sign in" and bind the handleLogin function to the click event. */}
    </Button>
  );
};

Creating a Sign-Out Component

Creating a Sign-out component is similar to creating a Sign-in component as outlined above. Follow these steps in your SignOutButton.jsx file to create the Sign-out component:

  • Import the useMsal hook from msal-react to access the MSAL instance for handling sign-out.
import { useMsal } from '@azure/msal-react';
  • Utilize the useMsal hook to access the MSAL instance. Create a variable called instance by using the useMsal hook. This allows access to the MSAL instance configured for your application.
const { instance } = useMsal();
  • Define the handleLogout function, which uses the instance.logoutPopup() method. This function triggers a pop-up for signing out the user and redirects them to the home page after signing out.
const handleLogout = () => {
  instance.logoutPopup();
};
  • Incorporate the handleLogout function into the onClick attribute of the button within the component.
<Button color="inherit" onClick={handleLogout}>
  Sign out
</Button>;

Here’s the SignOutButton.jsx file for reference:

import Button from "@mui/material/Button"; // Importing a button component from the Material-UI library.
import { useMsal } from "@azure/msal-react"; // Importing the useMsal hook from Azure MSAL for handling authentication.

export const SignOutButton = () => {
  const { instance } = useMsal(); // Access the instance object from the useMsal hook.

  const handleLogout = () => {
    instance.logoutPopup(); // Call the logoutPopup method from the instance object to initiate the sign-out process.
  };

  return (
    <Button color="inherit" onClick={handleLogout}>
      Sign out {/* Render a button with the label "Sign out" and bind the handleLogout function to the click event. */}
    </Button>
  );
};

Conditionally Rendering UI Elements Based on Authentication Status

To conditionally render UI elements in the application based on the user’s authentication status, follow these steps in the NavBar.jsx and Home.jsx files.

  • In the NavBar.jsx file:

Import useIsAuthenticated hook from msal-react. This hook allows for conditional elements rendering based on the user’s authentication status.

import { useIsAuthenticated } from "@azure/msal-react";

Conditionally render the WelcomeName element within the component based on the user’s authentication status.

// Conditional rendering: Display the WelcomeName component only if isAuthenticated is true.
{
  isAuthenticated ? <WelcomeName /> : null;
}

Conditionally render SignInButton and SignOutButton elements based on the user’s authentication status. Render the SignOutButton if authenticated and the SignInButton if not authenticated.

// Display the SignOutButton if isAuthenticated is true, otherwise display the SignInButton.
{
  isAuthenticated ? <SignOutButton /> : <SignInButton />;
}
  • In the Home.jsx file:

Implement conditional text rendering by leveraging AuthenticatedTemplate and UnauthenticatedTemplate components provided by msal-react. Import AuthenticatedTemplate and UnauthenticatedTemplate from msal-react.

import { AuthenticatedTemplate, UnauthenticatedTemplate } from "@azure/msal-react"

Enclose the Typography elements containing text intended for visibility when the user is signed in within AuthenticatedTemplate.

<AuthenticatedTemplate>
  <Typography variant="h6">
    You are signed-in. Select profile to call Microsoft Graph.
  </Typography>
</AuthenticatedTemplate>;

Wrap the Typography elements that should be visible when the user is signed out within UnauthenticatedTemplate.

<UnauthenticatedTemplate>
  <Typography variant="h6">
    Please sign in to see your profile information.
  </Typography>
</UnauthenticatedTemplate>;

Here’s a preview of the app that selectively presents specific information based on your sign-in status. Untitled design (7) (1)

Using Tokens

We’ll dive into the practical steps for acquiring access tokens and making authenticated API requests. Access tokens are the key to securely accessing external resources, and we’ll explore how to use them effectively in your application.

Acquiring access tokens

To acquire access tokens for making authenticated API requests, follow these steps in the Profile.jsx file:

  • Import the necessary dependencies at the beginning of the Profile.jsx file. These dependencies are required for handling authentication and acquiring access tokens.
import { useMsalAuthentication } from "@azure/msal-react";
import { InteractionType } from "@azure/msal-browser";
import { useEffect, useState } from "react";
  • Create a state variable called displayData using the useState hook. This state variable will store the retrieved data from the authenticated API
const [displayData, setDisplayData] = useState(null);
  • Utilize the useMsalAuthentication hook to acquire access tokens. This hook takes two parameters: the interaction type and an object specifying the requested scopes.
const { result, error } = useMsalAuthentication(InteractionType.Redirect, {
  scopes: ["user.read"],
});

To handle the access token and perform related actions, use the useEffect hook. This should run this effect when the component mounts. Inside this effect, you can run a series of checks:

  • Check if displayData exists to prevent unnecessary re-execution of the effect when data is already available.
  • Check for any authentication error and log it to the console for error handling.
  • Check if result exists, then extract the access token.
useEffect(() => {
  if (!displayData) {
    return;
  }
  if (error) {
    console.log(error);
    return;
  }
  if (result) {
    const accessToken = result.accessToken;
  }
}, [displayData, error, result]);

Making authenticated API requests

To make authenticated API requests and handle the response in the React application, follow these steps:

Create a new file named Fetch.js in the src folder to encapsulate the functions for making API requests.

  • In Fetch.js file:

Define a function called retrieveData that takes an endpoint and access token as parameters. This function will handle the API request.

export const retrieveData = (endpoint, accessToken) => {};

Inside the retrieveData function, create the Headers object and set the authorization header with the access token (bearer token).

const headers = new Headers();
const bearer = `Bearer ${accessToken}`;
headers.append("Authorization", bearer);

Create an options object with the HTTP method and headers.

const options = {
  method: "GET",
  headers: headers,
};

Use the fetch function to make the API request. Handle the response with .then() and catch any errors with .catch().

return fetch(endpoint, options)
  .then((response) => response.json())
  .catch((error) => console.log(error));

Here’s the Fetch.js file for reference:

export const retrieveData = (endpoint, accessToken) => {
  const headers = new Headers();
  const bearer = `Bearer ${accessToken}`;
  headers.append("Authorization", bearer);

  const options = {
    method: "GET",
    headers: headers,
  };

  return fetch(endpoint, options)
    .then((response) => response.json())
    .catch((error) => console.log(error));
};
  • In Profile.jsx:

Import the retrieveData function from Fetch.js. This prepares your profile component to utilize the functionality for making authenticated API requests.

import { retrieveData } from "../Fetch";

Use the retrieveData method to make an authenticated API request. For example, you can set the endpoint to ”https://graph.microsoft.com/v1.0/me“.

if (result) {
  const accessToken = result.accessToken;
  retrieveData("https://graph.microsoft.com/v1.0/me", accessToken)
    .then((response) => setDisplayData(response))
    .catch((error) => console.log(error));
}

In the component’s return statement, render the data (displayData) if it exists; otherwise, display nothing.

return <>{displayData ? <ProfileData displayData={displayData} /> : null}</>;

Here’s the Profile.jsx file for reference:

import { ProfileData } from "../components/ProfileData"; // Importing the ProfileData component
import { useMsalAuthentication } from "@azure/msal-react"; // Importing the useMsalAuthentication hook from Azure MSAL
import { InteractionType } from "@azure/msal-browser"; // Importing the InteractionType from Azure MSAL
import { useEffect, useState } from "react"; // Importing the useEffect and useState hooks from React
import { retrieveData } from "../Fetch"; // Importing the retrieveData function from a custom module

export const Profile = () => {
  const [displayData, setDisplayData] = useState(null); // Initializing a state variable displayData using useState

  const { result, error } = useMsalAuthentication(InteractionType.Redirect, {
    scopes: ["user.read"], // Configuring the useMsalAuthentication hook with a specified scope
  });

  useEffect(() => {
    if (!displayData) {
      return; // If displayData is already populated, do nothing
    }
    if (error) {
      console.log(error); // If there's an error, log it to the console
      return;
    }
    if (result) {
      const accessToken = result.accessToken; // Access the accessToken property from the result object
      retrieveData("https://graph.microsoft.com/v1.0/me", accessToken) // Call the retrieveData function with the access token
        .then((response) => setDisplayData(response)) // Set the displayData state with the response data
        .catch((error) => console.log(error)); // Handle and log any errors
    }
  }, [displayData, error, result]); // Run this effect when displayData, error, or result changes

  return <>{displayData ? <ProfileData displayData={displayData} /> : null}</>; // Conditional rendering of the ProfileData component based on the displayData state
};

Here’s a sneak peek of the app showcasing user-specific details when signed in.

Untitled design (3) (1)

Displaying the Name of the Signed-In User

To enhance the user experience by displaying the name of the signed-in user in the UI, follow these steps:

  • Inindex.js File:

Import the necessary dependencies and set up an MSAL event callback to handle successful login events.

import { PublicClientApplication, EventType } from "@azure/msal-browser";

// Add an MSAL event callback to set the active account
pubClientApp.addEventCallback((event) => {
  if (event.eventType === EventType.LOGIN_SUCCESS) {
    console.log(event);
    pubClientApp.setActiveAccount(event.payload.account);
  }
});
  • In WelcomeName.jsx Component:

Import the necessary dependencies for accessing the application instance and managing the component state.

import { useMsal } from "@azure/msal-react";
import { useState, useEffect } from "react";

Define a state variable, username, to store the username of the signed-in user.

const [username, setUsername] = useState('');

Use the useMsal hook to access the application instance (instance) created earlier.

const { instance } = useMsal();

Inside a useEffect hook, set the currentAccount to the active account and update the username state variable. Ensure you include instance in the dependency array to watch for changes.

useEffect(() => {
  const currentAccount = instance.getActiveAccount();
  if (currentAccount) {
    setUsername(currentAccount.username);
  }
}, [instance]);

In the component’s return statement, display the username in the UI using the Typography component.

return <Typography variant="h6">Welcome, {username}</Typography>;

A snapshot of the app revealing the username while signed in: jdd

Error Handling

To enable error handling and logging with MSAL in your application, follow these steps:

Open index.js File:

  • Within the pubClientApp object, include a cache object with options such as cacheLocation and storeAuthStateInCookie. These options help control how authentication artifacts are cached and managed.
cache: {
  cacheLocation: 'localStorage',
  storeAuthStateInCookie: false, 
}
  • Include a system object within the pubClientApp object, and define loggerOptions for logging configuration. This allows you to specify how MSAL should handle logs.
system: {
  loggerOptions: {
    loggerCallback: (level, message, containsPII) => {
      console.log(message); // Define a callback function to handle log messages (in this case, logging to the console).
    },
    logLevel: 'Verbose' // Set the log level to 'Verbose' (providing detailed logs including debug information).
  }
}

With these configurations in place, MSAL will log interactions, errors, and other information in the console. You can use this information for debugging and monitoring the authentication process.

Please note that this setup helps you debug and monitor authentication-related activities and troubleshoot any issues that may arise during user interactions with Azure AD.

A preview of the browser console logging the interactions. console.png

Handling authentication errors

To handle authentication errors and claims challenges in the MSAL app, follow these steps:

  • In index.js File:

Add the clientCapabilities option to the auth object. This option declares that the app is capable of handling claims challenges.

auth: {
  clientId: "5d804fed-8b0e-4c9b-b949-6020d4945ead",
  authority: "https://login.microsoftonline.com/consumers",
  redirectUri: "http://localhost:3000/",
  clientCapabilities: ['CP1']
},
  • In Fetch.js File: Add a function called handleClaims that checks the response for status codes.

    • If the status is 200 (indicating success), it returns the response as JSON.
    • If the status is 401, it checks if the response header includes “authenticated.” If it does, it extracts the claimsChallenge from the header and stores it in sessionStorage.
const handleClaims = (response) => {
  if (response.status === 200) {
    return response.json(); // If the response status is 200 (OK), parse it as JSON and return the result.
  } else if (response.status === 401) {
    if (response.headers.get("www-authenticate")) {
      const authenticateHeader = response.headers.get("www-authenticate");
      const claimsChallenge = authenticateHeader
        .split(" ")
        .find((entry) => entry.includes("claims=")) // Find the entry in the authenticateHeader that contains "claims=".
        .split('claims="')[1] // Extract the part of the entry after 'claims="'.
        .split('",')[0]; // Extract the part before the next '"'.
      sessionStorage.setItem("claimsChallenge", claimsChallenge); // Store the claims challenge in session storage.
      return; // Return without further processing.
    }
    throw new Error(`Error $(response.status)`); // If there's no 'www-authenticate' header, throw an error.
  } else {
    throw new Error(`Error $(response.status)`); // If the response status is neither 200 nor 401, throw an error.
  }
};

Modify the fetch call to pass the response through the handleClaims function. This ensures that the response is processed to handle claims challenges or other errors.

return fetch(endpoint, options)
  .then((response) => handleClaims(response))
  .catch((error) => console.log(error));
  • In Profile.jsx Component:

Add a parameter claims to the useMsalAuthentication configuration. This parameter is set to claimsChallenge stored in sessionStorage.

const { result, error } = useMsalAuthentication(InteractionType.Redirect, {
  scopes: ["user.read"],
  claims: sessionStorage.getItem("claimsChallenge")
    ? window.atob(sessionStorage.getItem("claimsChallenge"))
    : undefined,
});

These steps allow you to handle authentication errors and claims challenges in your MSAL app, making it more robust and capable of managing custom claims challenges during the authentication process.

Here’s a troubleshooting guide for addressing Microsoft Entra ID issues.

Conclusion

This comprehensive guide offers a step-by-step walkthrough of implementing authentication with MSAL-React. It equips readers to set up the development environment, configure MSAL-React, create sign-in and sign-out components, make authenticated API requests, and effectively handle authentication errors.

Secure Your Front-End: Detect, Fix, and Fortify

Spot abnormal user behaviors and iron out the bugs early with OpenReplay. Dive into session replays and reinforce your front-end against vulnerabilities that hackers search for.

OpenReplay