npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ra-auth-msal

v3.0.0

Published

An auth provider for [react-admin](https://github.com/marmelab/react-admin) that handles authentication using the [Microsoft Authentication Library (MSAL)](https://learn.microsoft.com/fr-fr/azure/active-directory/develop/msal-overview).

Downloads

2,324

Readme

ra-auth-msal

An auth provider for react-admin that handles authentication using the Microsoft Authentication Library (MSAL).

This is useful when using Azure Active Directory to authenticate your users.

This package provides:

  • An msalAuthProvider function to get the auth provider
  • An msalHttpClient helper to get a fetch-like function that adds the access token to the request
  • A custom LoginPage component that displays a loading indicator if the redirection takes too long

Supported MSAL Features

  • Sign-in using any authentication flow supported by MSAL for Single Page Apps (Authorization Code and Implicit Grant), with the ability to configure the scopes and optional claims
  • Get an access token for the user, with the ability to configure the scopes and optional claims, allowing for instance to query a Microsoft Graph endpoint
  • Use the user's roles and groups to compute permissions
  • Automatic token refresh

Installation

yarn add ra-auth-msal
# or
npm install --save ra-auth-msal

Basic Usage

// in src/authConfig.js
export const msalConfig = {
  auth: {
    // 'Application (client) ID' of app registration in Azure portal - this value is a GUID
    clientId: "12345678-1234-1234-1234-123456789012",
    // Full directory URL, in the form of https://login.microsoftonline.com/<tenant-id>
    authority: "https://login.microsoftonline.com/common",
    // Full redirect URL, in form of http://localhost:8080/auth-callback
    redirectUri: "http://localhost:8080/auth-callback",
    // We need to disable this feature because it is already handled by react-admin, and would otherwise conflict
    navigateToLoginRequestUrl: false,
  },
  cache: {
    cacheLocation: "sessionStorage",
    storeAuthStateInCookie: false,
  },
};
// in src/App.jsx
import React, { useEffect } from "react";
import { Admin, Resource } from 'react-admin';
import { BrowserRouter } from "react-router-dom";
import { PublicClientApplication } from "@azure/msal-browser";
import { LoginPage, msalAuthProvider } from "ra-auth-msal";
import dataProvider from './dataProvider';
import posts from './posts';
import { msalConfig } from "./authConfig";

const myMSALObj = new PublicClientApplication(msalConfig);

const App = () => {
  const [isMSALInitialized, setMSALInitialized] = React.useState(false);
  useEffect(() => {
    myMSALObj.initialize().then(() => {
      setMSALInitialized(true);
    });
  }, []);

  const authProvider = msalAuthProvider({
    msalInstance: myMSALObj,
  });

  if (!isMSALInitialized) {
    return <div>Loading...</div>;
  }

  return (
    <BrowserRouter>
       <Admin
           authProvider={authProvider}
           dataProvider={dataProvider}
           title="Example Admin"
           loginPage={LoginPage}
        >
            <Resource name="posts" {...posts} />
      </Admin>
    </BrowserRouter>
   );
};
export default App;

Tip: You need to wrap your <Admin> component in a <BrowserRouter> for this library to work. Indeed, MSAL uses a hash-based routing strategy when redirecting back to your app, which is incompatible with a <HashRouter>.

Advanced Usage

Handling Permissions

// in src/authConfig.js
export const msalConfig = {
  // ...
};

/**
 * Customize this map to match your own roles and permissions
 */
const rolesPermissionMap = {
  "12345678-1234-1234-1234-123456789012": "user",
  "12345678-1234-1234-1234-123456789013": "admin",
};

/**
 * Custom function to map roles to permissions, using the rolesPermissionMap above.
 * Alternatively, you can use the MS Graph API to get more information about the user's roles and groups.
 */
export const getPermissionsFromAccount = async (account) => {
  const roles = account?.idTokenClaims?.roles ?? [];
  return roles.map((role) => rolesPermissionMap[role]);
};
// in src/App.jsx
import React, { useEffect } from "react";
import { Admin, Resource } from 'react-admin';
import { BrowserRouter } from "react-router-dom";
import { PublicClientApplication } from "@azure/msal-browser";
import { LoginPage, msalAuthProvider } from "ra-auth-msal";
import dataProvider from './dataProvider';
import posts from './posts';
import { msalConfig, getPermissionsFromAccount } from "./authConfig";

const myMSALObj = new PublicClientApplication(msalConfig);

const App = () => {
  const [isMSALInitialized, setMSALInitialized] = React.useState(false);
  useEffect(() => {
    myMSALObj.initialize().then(() => {
      setMSALInitialized(true);
    });
  }, []);

  const authProvider = msalAuthProvider({
    msalInstance: myMSALObj,
    getPermissionsFromAccount,
  });

  if (!isMSALInitialized) {
    return <div>Loading...</div>;
  }

  return (
    <BrowserRouter>
       <Admin
           authProvider={authProvider}
           dataProvider={dataProvider}
           title="Example Admin"
           loginPage={LoginPage}
        >
            <Resource name="posts" {...posts} />
      </Admin>
    </BrowserRouter>
   );
};
export default App;

Handling User Identity

// in src/authConfig.js
export const msalConfig = {
  // ...
};

/**
 * Custom function to get the identity from the account info.
 */
export const getIdentityFromAccount = async (account) => {
  return {
    id: account?.localAccountId,
    fullName: account?.username,
  };
};
// in src/App.jsx
import React, { useEffect } from "react";
import { Admin, Resource } from 'react-admin';
import { BrowserRouter } from "react-router-dom";
import { PublicClientApplication } from "@azure/msal-browser";
import { LoginPage, msalAuthProvider } from "ra-auth-msal";
import dataProvider from './dataProvider';
import posts from './posts';
import { msalConfig, getIdentityFromAccount } from "./authConfig";

const myMSALObj = new PublicClientApplication(msalConfig);

const App = () => {
  const [isMSALInitialized, setMSALInitialized] = React.useState(false);
  useEffect(() => {
    myMSALObj.initialize().then(() => {
      setMSALInitialized(true);
    });
  }, []);

  const authProvider = msalAuthProvider({
    msalInstance: myMSALObj,
    getIdentityFromAccount,
  });

  if (!isMSALInitialized) {
    return <div>Loading...</div>;
  }

  return (
    <BrowserRouter>
       <Admin
           authProvider={authProvider}
           dataProvider={dataProvider}
           title="Example Admin"
           loginPage={LoginPage}
        >
            <Resource name="posts" {...posts} />
      </Admin>
    </BrowserRouter>
   );
};
export default App;

Custom Login Request

// in src/authConfig.js
export const msalConfig = {
  // ...
};

/**
 * Scopes you add here will be prompted for user consent during sign-in.
 * By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request.
 * For more information about OIDC scopes, visit:
 * https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
 */
export const loginRequest = {
  scopes: ["User.Read"],
};
// in src/App.jsx
import React, { useEffect } from "react";
import { Admin, Resource } from 'react-admin';
import { BrowserRouter } from "react-router-dom";
import { PublicClientApplication } from "@azure/msal-browser";
import { LoginPage, msalAuthProvider } from "ra-auth-msal";
import dataProvider from './dataProvider';
import posts from './posts';
import { msalConfig, loginRequest } from "./authConfig";

const myMSALObj = new PublicClientApplication(msalConfig);

const App = () => {
  const [isMSALInitialized, setMSALInitialized] = React.useState(false);
  useEffect(() => {
    myMSALObj.initialize().then(() => {
      setMSALInitialized(true);
    });
  }, []);

  const authProvider = msalAuthProvider({
    msalInstance: myMSALObj,
    loginRequest,
  });

  if (!isMSALInitialized) {
    return <div>Loading...</div>;
  }

  return (
    <BrowserRouter>
       <Admin
           authProvider={authProvider}
           dataProvider={dataProvider}
           title="Example Admin"
           loginPage={LoginPage}
        >
            <Resource name="posts" {...posts} />
      </Admin>
    </BrowserRouter>
   );
};
export default App;

Custom Token Request

// in src/authConfig.js
export const msalConfig = {
  // ...
};

/**
 * Add here the scopes to request when obtaining an access token for MS Graph API. For more information, see:
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md
 */
export const tokenRequest = {
  scopes: ["User.Read"],
  forceRefresh: false, // Set this to "true" to skip a cached token and go to the server to get a new token
};
// in src/App.jsx
import React, { useEffect } from "react";
import { Admin, Resource } from 'react-admin';
import { BrowserRouter } from "react-router-dom";
import { PublicClientApplication } from "@azure/msal-browser";
import { LoginPage, msalAuthProvider } from "ra-auth-msal";
import dataProvider from './dataProvider';
import posts from './posts';
import { msalConfig, tokenRequest } from "./authConfig";

const myMSALObj = new PublicClientApplication(msalConfig);

const App = () => {
  const [isMSALInitialized, setMSALInitialized] = React.useState(false);
  useEffect(() => {
    myMSALObj.initialize().then(() => {
      setMSALInitialized(true);
    });
  }, []);

  const authProvider = msalAuthProvider({
    msalInstance: myMSALObj,
    tokenRequest,
  });

  if (!isMSALInitialized) {
    return <div>Loading...</div>;
  }

  return (
    <BrowserRouter>
       <Admin
           authProvider={authProvider}
           dataProvider={dataProvider}
           title="Example Admin"
           loginPage={LoginPage}
        >
            <Resource name="posts" {...posts} />
      </Admin>
    </BrowserRouter>
   );
};
export default App;

redirectOnCheckAuth

You can choose whether the authProvider should redirect to the MS login form when the user is not authenticated. By default, it is set to true.

It can be useful to set it to false when you want to trigger the redirection only from a custom login page.

// in src/authConfig.js
export const msalConfig = {
  // ...
};
// in src/CustomLoginPage.jsx
import * as React from "react";
import { Button } from "@mui/material";
import { useLogin } from "react-admin";

/**
 * Csutom Login Page used to trigger the redirection to the MS login page.
 */
export const CustomLoginPage = () => {
  const login = useLogin();
  return (
    <div>
      <Button onClick={() => login({})}>
          Sign in with Microsoft
      </Button>
    </div>
  );
};
// in src/App.jsx
import React, { useEffect } from "react";
import { Admin, Resource } from 'react-admin';
import { BrowserRouter } from "react-router-dom";
import { PublicClientApplication } from "@azure/msal-browser";
import { msalAuthProvider } from "ra-auth-msal";
import { CustomLoginPage } from "./CustomLoginPage";
import dataProvider from './dataProvider';
import posts from './posts';
import { msalConfig } from "./authConfig";

const myMSALObj = new PublicClientApplication(msalConfig);

const App = () => {
  const [isMSALInitialized, setMSALInitialized] = React.useState(false);
  useEffect(() => {
    myMSALObj.initialize().then(() => {
      setMSALInitialized(true);
    });
  }, []);

  const authProvider = msalAuthProvider({
    msalInstance: myMSALObj,
    redirectOnCheckAuth: false,
  });

  if (!isMSALInitialized) {
    return <div>Loading...</div>;
  }

  return (
    <BrowserRouter>
       <Admin
           authProvider={authProvider}
           dataProvider={dataProvider}
           title="Example Admin"
           loginPage={CustomLoginPage}
        >
            <Resource name="posts" {...posts} />
      </Admin>
    </BrowserRouter>
   );
};
export default App;

enableDeepLinkRedirect

You can choose whether the authProvider should redirect to the page the user was visiting once the user has been authenticated (this allows easier URL sharing between users). By default, it is set to true.

Note: This features relies on sessionStorage and is not available with Server-Side Rendering.

You can disable this feature like this:

// in src/authConfig.js
export const msalConfig = {
  // ...
};
// in src/App.jsx
import React, { useEffect } from "react";
import { Admin, Resource } from 'react-admin';
import { BrowserRouter } from "react-router-dom";
import { PublicClientApplication } from "@azure/msal-browser";
import { msalAuthProvider } from "ra-auth-msal";
import { CustomLoginPage } from "./CustomLoginPage";
import dataProvider from './dataProvider';
import posts from './posts';
import { msalConfig } from "./authConfig";

const myMSALObj = new PublicClientApplication(msalConfig);

const App = () => {
  const [isMSALInitialized, setMSALInitialized] = React.useState(false);
  useEffect(() => {
    myMSALObj.initialize().then(() => {
      setMSALInitialized(true);
    });
  }, []);

  const authProvider = msalAuthProvider({
    msalInstance: myMSALObj,
    enableDeepLinkRedirect: false,
  });

  if (!isMSALInitialized) {
    return <div>Loading...</div>;
  }

  return (
    <BrowserRouter>
       <Admin
           authProvider={authProvider}
           dataProvider={dataProvider}
           title="Example Admin"
        >
            <Resource name="posts" {...posts} />
      </Admin>
    </BrowserRouter>
   );
};
export default App;

msalHttpClient

ra-auth-msal includes an msalHttpClient that can be used to make authenticated requests to your API. This helper automatically adds the accessToken to the request headers.

Here is an example with ra-data-json-server:

// in src/authConfig.js
export const msalConfig = {
  // ...
};

/**
 * Add here the scopes to request when obtaining an access token for MS Graph API. For more information, see:
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md
 */
export const tokenRequest = {
  scopes: ["User.Read"],
  forceRefresh: false, // Set this to "true" to skip a cached token and go to the server to get a new token
};
// in src/App.jsx
import React, { useEffect } from "react";
import { Admin, Resource } from 'react-admin';
import { BrowserRouter } from "react-router-dom";
import { PublicClientApplication } from "@azure/msal-browser";
import { LoginPage, msalAuthProvider, msalHttpClient } from "ra-auth-msal";
import jsonServerProvider from "ra-data-json-server";
import posts from './posts';
import { msalConfig, tokenRequest } from "./authConfig";

const myMSALObj = new PublicClientApplication(msalConfig);

const App = () => {
  const [isMSALInitialized, setMSALInitialized] = React.useState(false);
  useEffect(() => {
    myMSALObj.initialize().then(() => {
      setMSALInitialized(true);
    });
  }, []);

  const authProvider = msalAuthProvider({
    msalInstance: myMSALObj,
    tokenRequest
  });

  const httpClient = msalHttpClient({
    msalInstance: myMSALObj,
    tokenRequest
  });

  const dataProvider = jsonServerProvider(
    "https://jsonplaceholder.typicode.com",
    httpClient
  );

  if (!isMSALInitialized) {
    return <div>Loading...</div>;
  }

  return (
    <BrowserRouter>
       <Admin
           authProvider={authProvider}
           dataProvider={dataProvider}
           title="Example Admin"
           loginPage={LoginPage}
        >
            <Resource name="posts" {...posts} />
      </Admin>
    </BrowserRouter>
   );
};
export default App;

Tip: By default msalHttpClient will use the accessToken. If you want to use the idToken instead, you will need to provide your own httpClient like so:

import { fetchUtils } from "react-admin";

const myHttpClient = ({ msalInstance, tokenRequest }) => async (url, options = {}) => {
  const account = msalInstance.getActiveAccount();
  const authResult = await msalInstance.acquireTokenSilent({
    account,
    ...tokenRequest,
  });
  const token = authResult && authResult.idToken;
  const user = { authenticated: !!token, token: `Bearer ${token}` };
  return fetchUtils.fetchJson(url, { ...options, user });
};

refreshAuth

The authProvider already supports automatic refresh of the token. However, if your dataProvider passes the token to your API, you should wrap it with addRefreshAuthToDataProvider to ensure it also refreshes the token when needed:

import { addRefreshAuthToDataProvider } from 'react-admin';
import { msalRefreshAuth } from "ra-auth-msal";
import { PublicClientApplication } from "@azure/msal-browser";
import { msalConfig, tokenRequest } from "./authConfig";
import { dataProvider } from './dataProvider';

const myMSALObj = new PublicClientApplication(msalConfig);

const dataProvider = addRefreshAuthToDataProvider(
    dataProvider,
    msalRefreshAuth({
      msalInstance: myMSALObj,
      tokenRequest,
    })
);

Demo

You can find a working demo, along with the source code, in this project's repository: https://github.com/marmelab/ra-auth-msal

License

This auth provider is licensed under the MIT License and sponsored by marmelab.