/react-aad

A React wrapper for Azure AD using the Microsoft Authentication Library (MSAL). The easiest way to integrate AzureAD with your React for authentication.

Primary LanguageTypeScript

The easiest way to integrate the Microsoft Authentication Library (MSAL) with React applications. Secure your apps with Azure Active Directory (AAD)

npm version build status dependencies npm

🐵 Our code monkeys live on a solid diet of ⭐'s. If you like what they're doing, please feed them!

React AAD MSAL

A library of components to easily integrate the Microsoft Authentication Library with Azure Active Directory in your React app quickly and reliably. The library focuses on flexibility, providing functionality to login, logout, and fetch the user details while maintaining access to the underlying MSAL library for advanced use.

❗ This library is not affiliated with the Identity team at Microsoft. It was developed as a tool for the Open Source community to use and contribute to as they see fit.

📝 Table of contents

🎉 Features

✅ Login/logout with AzureAD component
✅ Callback functions for login success, logout success, and user info changed
withAuthentication higher order component for protecting components, routes, or the whole app
✅ Function as Child Component pattern (FaCC) to pass authentication data and login/logout functions to children components
✅ Redux integration for storing authentication status, user info, tokens, etc
✅ Automatic renewal of IdTokens, and optional function to get a fresh token at any point
✅ Easily fetch a fresh Access Token from cache (or refresh it) before calling API endpoints
✅ Various build types including ES6, CommonJS, and UMD

🏁 Getting Started

Prerequisites

Installation

Via NPM:

npm install react-aad-msal msal --save

Creating the Provider

Before beginning it is required to configure an instance of the MsalAuthProvider and give it three parameters:

Parameters Description
config Instance of a Msal.Configuration object to configure the underlying provider. The documentation for all the options can be found in the configuration options doc
parameters Instance of the Msal.AuthenticationParameters configuration to identify how the authentication process should function. This object includes the scopes values. You can see possible values for scopes here
options [Optional] The options are defined by the IMsalAuthProviderConfig interface. This contains settings that describe how the authentication provider should handle certain authentication processes.

The MsalAuthProvider is meant to be a singleton. There are known implications when multiple instances of MSAL are running at the same time. The recommended approach is to instantiate the MsalAuthProvider in a separate file and import it when needed.

// authProvider.js
import { MsalAuthProvider, LoginType } from 'react-aad-msal';

// Msal Configurations
const config = {
  auth: {
    authority: 'https://login.microsoftonline.com/common',
    clientId: '<YOUR APPLICATION ID>',
    redirectUri: '<OPTIONAL REDIRECT URI>'
  },
  cache: {
    cacheLocation: "localStorage",
    storeAuthStateInCookie: true
  }
};

// Authentication Parameters
const authenticationParameters = {
  scopes: [
    '<property (i.e. user.read)>',
    'https://<your-tenant-name>.onmicrosoft.com/<your-application-name>/<scope (i.e. demo.read)>'
  ]
}

// Options
const options = {
  loginType: LoginType.Popup,
  tokenRefreshUri: window.location.origin + '/auth.html'
}

export const authProvider = new MsalAuthProvider(config, authenticationParameters, options)

Now you can import the authProvider and use it in combination with one of the authentication components.

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { AzureAD } from 'react-aad-msal';

import App from './App';
import { authProvider } from './authProvider';

ReactDOM.render(
  <AzureAD provider={authProvider} forceLogin={true}>
    <App />
  </AzureAD>,
  document.getElementById('root'),
);

Msal Configuration

The options that get passed to the MsalAuthProvider are defined by the MSAL library, and are described in more detail in the configuration options documentation.

Below is the total set of configurable options that are supported currently in the config.

  // Configuration Object
  export type Configuration = {
    auth: AuthOptions,
    cache?: CacheOptions,
    system?: SystemOptions
  };

  // Protocol Support
  export type AuthOptions = {
    clientId: string;
    authority?: string;
    validateAuthority?: boolean;
    redirectUri?: string | (() => string);
    postLogoutRedirectUri?: string | (() => string);
    navigateToLoginRequestUrl?: boolean;
  };

  // Cache Support
  export type CacheOptions = {
    cacheLocation?: CacheLocation;
    storeAuthStateInCookie?: boolean;
  };

  // Library support
  export type SystemOptions = {
    logger?: Logger;
    loadFrameTimeout?: number;
    tokenRenewalOffsetSeconds?: number;
    navigateFrameWait?: number;
  };

For more information on MSAL config options refer to the MSAL configuration options documentation.

Authentication Parameters

When instantiating an instance of the MsalAuthProvider the authentication parameters passed will become the default parameters used when authenticating and fetching or refreshing tokens. It is possible to change the default parameters later by executing the setAuthenticationParameters() method on the MsalAuthProvider.

The set of options that are supported for the Msal.AuthenticationParameters class can be found below as they are defined in the MSAL library.

  export type AuthenticationParameters = {
    scopes?: Array<string>;
    extraScopesToConsent?: Array<string>;
    prompt?: string;
    extraQueryParameters?: {[key: string]: string};
    claimsRequest?: string;
    authority?: string;
    state?: string;
    correlationId?: string;
    account?: Account;
    sid?: string;
    loginHint?: string;
  };

Options

The options parameter defines settings related to how the authentication provider processes authentication operations provided by the MSAL library.

const options = {
  // The default login type is Popup
  loginType: LoginType.Popup,
  // A blank html page that MSAL can load in an iframe to refresh the token
  // The default setting for this parameter is `window.location.origin`
  tokenRefreshUri: window.location.origin + '/auth.html',
};

LoginType is an enum with two options for Popup or Redirect authentication. This parameter is optional and will default to Popup if not provided. The tokenRefreshUri allows you to set a separate page to load only when tokens are being refreshed. When MSAL attempts to refresh a token, it will reload the page in an iframe. This option allows you to inform MSAL of a specific page it can load in the iframe. It is best practice to use a blank HTML file so as to prevent all your site scripts and contents from loading multiple times.

At any time after instantiating the MsalAuthProvider the login type can be changed using the setProviderOptions() method. You may also retrieve the current options using the getProviderOptions() method.

📦 Authentication Components

The library provides multiple components to integrate Azure AD authentication into your application and each component has various use cases. The are also plans for additional components, documented in the project Roadmap.

AzureAD Component

The AzureAD component is the primary method to add authentication to your application. When the component is loaded it internally uses MSAL to check the cache and determine the current authentication state. The users authentication status determines how the component will render the children.

  1. If the children is an element, it will only be rendered when the AzureAD detects an authenticated user.
  2. If the children is a function, then it will always be executed with the following argument:
    {
      login, // login function
      logout, // logout function
      authenticationState, // the current authentication state
      error, // any error that occured during the login process
      accountInfo, // account info of the authenticated user
    }

The AzureAD component will check that the IdToken is not expired before determining that the user is authenticated. If the token has expired, it will attempt to renew it silently. If a valid token is maintained it will be sure there is an active Access Token available, otherwise it will refresh silently. If either of the tokens cannot be refreshed without user interaction, the user will be prompted to signin again.

import { AzureAD, AuthenticationState, AuthenticationState } from 'react-aad-msal';

// Import the provider created in a different file
import { authProvider } from './authProvider';

// Only authenticated users can see the span, unauthenticated users will see nothing
<AzureAD provider={authProvider}>
  <span>Only authenticated users can see me.</span>
</AzureAD>

// If the user is not authenticated, login will be initiated and they will see the span when done
<AzureAD provider={authProvider} forceLogin={true}>
  <span>Only authenticated users can see me.</span>
</AzureAD>

// Using a function inside the component will give you control of what to show for each state
<AzureAD provider={authProvider} forceLogin={true}>
  {
    ({login, logout, authenticationState, error, accountInfo}) => {
      switch (authenticationState) {
        case AuthenticationState.Authenticated:
          return (
            <p>
              <span>Welcome, {accountInfo.account.name}!</span>
              <button onClick={logout}>Logout</button>
            </p>
          );
        case AuthenticationState.Unauthenticated:
          return (
            <div>
              {error && <p><span>An error occured during authentication, please try again!</span></p>}
              <p>
                <span>Hey stranger, you look new!</span>
                <button onClick={login}>Login</button>
              </p>
            </div>
          );
        case AuthenticationState.InProgress:
          return (<p>Authenticating...</p>);
      }
    }
  }
</AzureAD>

The following props are available to the AzureAD component.

AzureAD Props Description
provider An MsalAuthProvider instance containing configuration values for your Azure AD instance. See [Creating the Provider](#Creating the Provider) for details.
authenticatedFunction ⚠️ [Deprecated] [Optional] A user defined callback function for the AzureAD component to consume. This function receives the AzureAD components logout function which you can use to trigger a logout. The return value will be rendered by the AzureAD component. If no return value is provided, the children of the AzureAD component will be rendered instead.
unauthenticatedFunction ⚠️ [Deprecated] [Optional] A user defined callback function for the AzureAD component to consume. This function receives the AzureAD components login function which you can then use to trigger a login. The return value will be rendered by the AzureAD component.
accountInfoCallback ⚠️ [Deprecated] [Optional] A user defined callback function for the AzureAD component to consume. The AzureAD component will call this function when login is complete to pass back the user info as an instance of IAccountInfo. In addition to the tokens, the account info includes the Msal.Account
reduxStore [Optional] You can provide a redux store which the component will dispatch actions to when changes occur. You can find the list of all actions defined in the AuthenticationActions enum.
forceLogin [Optional] A boolean that identifies whether the login process should be invoked immediately if the current user is unauthenticated. Defaults to false.

Higher Order Component

Sometimes it's easier to utilize a Higher Order Component (HoC) to lock down an app or page component with authentication. This can be accomplished using the withAuthentication component which acts as a wrapper for the AzureAD component.

import { withAuthentication } from 'react-aad-msal';

// The instance of MsalAuthProvider defined in a separate file
import { authProvider } from './authProvider.js';

// The App component wrapped in the withAuthentication() HoC
export default withAuthentication(App, {
 provider: authProvider,
 reduxStore: store
});

The first parameter is the component that requires authentication before being mounted. The second parameter is an object containing the props to be passed into the AzureAD component. With this approach the forceLogin prop will default to true. This is a good way to protect routes or quickly require authentication for your entire App in several lines.

🎓 Advanced Topics

Getting Tokens for API Requests

The library components will manage authenticating the user without you needing to think about tokens. But there are scenarios where a fresh token will be needed to communicate with a service or to decode the token to examine the claims. The library exposes methods for retrieving active IdTokens and Access Tokens.

For more advanced scenarios where you need specific control over error handling when a token fails to renew you can always access the MSAL API methods and renew a token manually as described in the MSAL token renewal pattern documentation.

Refreshing Access Tokens

To get a fresh and valid Access Token to pass to an API you can call the getAccessToken() on the MsalAuthProvider instance. This function will asynchronously attempt to retrieve the token from the cache. If the cached token has expired it will automatically attempt to refresh it. In some scenarios the token refresh will fail and the user will be required to authenticate again before a fresh token is provided. The method will handle these scenarios automatically.

The getAccessToken() returns an instance of the AccessTokenResponse class. The following snippet is an example of how you might use the function before calling an API endpoint.

// authProvider.js
import { MsalAuthProvider, LoginType } from 'react-aad-msal';
export const authProvider = new MsalAuthProvider(
  {
    /* Msal configurations */
  },
  {
    /* Authentication parameters */
  },
  {
    /* Options */
  },
);

// api.js
import { authProvider } from './authProvider';

const request = async url => {
  const token = await authProvider.getAccessToken();

  return fetch(url, {
    method: 'GET',
    headers: {
      Authorization: 'Bearer ' + token.accessToken,
      'Content-Type': 'application/json',
    },
  });
};

Renewing IdTokens

To get a fresh and valid IdToken you can call the getIdToken() on the MsalAuthProvider instance. This function will asynchronously attempt to retrieve the token from the cache. If the cached token has expired it will automatically attempt to renew it. In some scenarios the token renewal will fail and the user will be required to authenticate again before a new token is provided. The method will handle these scenarios automatically.

The getIdToken() returns an instance of the IdTokenResponse class. The following snippet is an example of how you might use the function to retrieve a valid IdToken.

// authProvider.js
import { MsalAuthProvider, LoginType } from 'react-aad-msal';
export const authProvider = new MsalAuthProvider(
  {
    /* Msal configurations */
  },
  {
    /* Authentication parameters */
  },
  {
    /* Options */
  },
);

// consumer.js
import { authProvider } from './authProvider';
const token = await authProvider.getIdToken();
const idToken = token.idToken.rawIdToken;

Integrating with a Redux Store

The AzureAD component optionally accepts a reduxStore prop. On successful login and after an Access Token has been acquired, an action of type AAD_LOGIN_SUCCESS will be dispatch to the provided store containing the token and user information returned from Active Directory. It does the same for logout events, but the action will not contain a payload.

Import your store into the file rendering the AzureAD component and pass it as a prop:

// authProvider.js
import { MsalAuthProvider, LoginType } from 'react-aad-msal';
export const authProvider = new MsalAuthProvider(
  {
    /* Msal configurations */
  },
  {
    /* Authentication parameters */
  },
  {
    /* Options */
  },
);

// index.js
import { authProvider } from './authProvider';
import { store } from './reduxStore.js';

// ...

ReactDOM.render(
  <AzureAD provider={authProvider} reduxStore={store}>
    <App />
  </AzureAD>,
  document.getElementById('root'),
);

Add a case to handle AAD_LOGIN_SUCCESS and AAD_LOGOUT_SUCCESS actions in a reducer file:

const initialState = {
  aadResponse: null,
};

const sampleReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'AAD_LOGIN_SUCCESS':
      return { ...state, aadResponse: action.payload };
    case 'AAD_LOGOUT_SUCCESS':
      return { ...state, aadResponse: null };
    default:
      return state;
  }
};

In addition to login and logout actions, the MsalAuthProvider will dispatch other actions for for various state changes. The full list can be found in the AuthenticationActions.ts file and in the table below. An example of a fully implemented sample reducer can be found in the sample project.

Action Type Payload Description
AAD_INITIALIZING None Dispatched when the MsalAuthProvider is instantiated and begins checking the cache to determine if the user is authenticated
AAD_INITIALIZED None Signifies that the MsalAuthProvider has successfully determined the authentication status of the user after being instantiated. It is safest not to use any state until initialization has completed
AAD_AUTHENTICATED_STATE_CHANGED AuthenticationState Dispatched whenever the user's authentication status changes
AAD_ACQUIRED_ID_TOKEN_SUCCESS IdTokenResponse Identifies that the IdToken has been retrieved or renewed successfully
AAD_ACQUIRED_ID_TOKEN_ERROR Msal.AuthError Dispatched when an error occurred while attempting to retrieve or renew the IdToken
AAD_ACQUIRED_ACCESS_TOKEN_SUCCESS AccessTokenResponse Identifies that the Access Token has been retrieved or refreshed successfully
AAD_ACQUIRED_ACCESS_TOKEN_ERROR Msal.AuthError Dispatched when an error occurred while attempting to retrieve or refresh the Access Token
AAD_LOGIN_SUCCESS IAccountInfo Dispatched when the user has been authenticated and a valid Access Token has been acquired
AAD_LOGIN_FAILED None Dispatched when the authentication process fails
AAD_LOGIN_ERROR Msal.AuthError Identifies that an error occurred while login was in process
AAD_LOGOUT_SUCCESS None Dispatched when the user has successfully logged out on the client side

Accessing the MSAL API

While this wrapper attempts to provide a full-featured means of authenticating with AzureAD, for advanced cases you may want to access the underlying MSAL API. The MsalAuthProvider extends the MSAL UserAgentApplication class and will give you access to all the functions available, in addition to implementing new methods used by the library components.

const authProvider = new MsalAuthProvider(config, authenticationParameters);
// authProvider provides access to MSAL features

It is not recommended to use this method if it can be avoided, since operations executed via MSAL may not reflect in the wrapper.

💿 Sample applications

If you'd like to see a sample application running please see the samples applications, built with Create React App.

The project can be built with the following steps:

  1. git clone https://github.com/syncweek-react-aad/react-aad.git
  2. cd ./react-aad
  3. Install the dependencies:
    • yarn install
  4. Build the react-aad library:
    • yarn build
  5. Run the default sample project from the project root using yarn start, or navigate to the sample folder you want and use yarn start directly.

Be sure to modify the authProvider.js configuration to specify your own ClientID and Authority.

📆 Roadmap

While the library is ready for use there is still plenty of ongoing work. The following is a list of a few of the improvements under consideration.

◽ Rewrite the sample app to use hooks and simplify the logic. ◽ Add a typescript sample application. ◽ Add a useAuthentication() hook to the library. :white_medium_small_square: Replace the AzureAD render props with event handlers. :white_medium_small_square: Add Context API provider. :white_medium_small_square: Add samples for consuming a Web API. :white_medium_small_square: Improve unit test coverage across the library. :white_medium_small_square: Maintain feature parity between the official MSAL Angular library after it undergoes its planned upgrade.

📚 Resources

The following resources may be helpful and provide further insight. If you've written a blog post, tutorial, or article feel free to create an issue so we can include it.

🏆 Contributers

This library is built with ❤️ by members of the open source community. To become a contributer, please see the contribution guidelines.