使用微软MSAL库在React中进行身份验证

使用微软MSAL库在React中进行身份验证

在当今的应用程序中,身份验证是强制性的,而微软的身份验证库(MSAL)是解决这一问题的有力方案。本文将向您展示在您自己的 React 代码中实现 MSAL 的所有细节。

在当今的数字环境中,确保用户数据的安全和隐私至关重要。无论您是在构建 Web 应用程序、移动应用程序还是其他任何需要用户身份验证的软件,Microsoft 的 React Authentication LibraryMSAL-React )都能提供一个强大的解决方案来简化这一过程。通过 MSAL-React,开发人员可以将安全身份验证无缝集成到他们的应用程序中,为用户提供安全、友好的使用体验。

在本综合指南中,我们将一步一步地指导您使用 MSAL-React 实施身份验证,帮助您利用微软身份验证平台的强大功能来保护用户数据并提高应用程序的可信度。无论您是经验丰富的开发人员还是刚刚开始工作,本文都将为您提供相关知识和工具,帮助您在 React 应用程序中采用强大的身份验证功能。

设置开发环境

构建应用程序所需的软件和工具有:

  • Node.jsnpm:从官方网站安装 Node.js,其中包括 npmnpm 用于管理依赖关系和运行脚本。
  • 复制模板: 该模板已构建了单页前端。下面是复制的方法:
    • 首先,fork 仓库
      打开终端,在终端中运行此命令克隆版本库。
git clone <your_repository_url>

在终端中使用此命令导航到项目目录。

cd msal-react-demo-template

导航到应用程序目录后,安装依赖项。

npm install

启动应用程序。

npm start

下面是该程序用户界面的初始外观。

用户界面的初始外观

npm install --save @azure/msal-browser @azure/msal-react

项目结构如下:

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 应用程序注册

Azure Active Directory(Azure AD)是微软基于云的身份和访问管理服务。它提供了一个全面的解决方案,用于管理用户身份并确保对云中和企业内部应用程序和资源的访问安全。在本指南中,Azure AD 对于为应用程序添加身份验证和授权至关重要,可确保只有授权用户才能访问资源。

创建 Azure AD 应用程序注册并获取客户端 ID

  • 访问 https://portal.azure.com/
  • 使用 Microsoft 帐户登录或创建一个帐户。
  • 在搜索栏中搜索 “App Registration”。
  • 点击 “New Registration”。
  • 填写要用于应用程序的名称。
  • 选择支持的帐户类型。本文仅使用 Microsoft 个人账户。
  • 对于重定向 URI,选择 “Single-page application (SPA)”,并将 URI 设置为 http://localhost:3000/
  • 点击 “Register”。
  • 在 “Overview” 菜单上,可以复制客户端 ID。

MSAL-React 集成

在应用程序中配置 MSAL-React,以启用安全且用户友好的基于 Azure AD 的身份验证和授权。

为 MSAL 设置配置

index.js 文件中,您将按照以下步骤配置将 MSAL-React 集成到 React 应用程序中。

  1. 导入必要的库:首先,从 msal-browser 中导入 PublicClientApplicationMSAL (Microsoft Authentication Library,微软身份验证库)是一个便于 Azure AD 身份验证和授权的库。
import { PublicClientApplication } from '@azure/msal-browser';
  1. 实例化 pubClientApp 对象并提供配置选项:通过配置基本选项创建 pubClientApp 对象。这些选项定义了应用程序与 Azure AD 的交互方式。下面是对每个选项的解释:
  • clientId:这是从 Azure AD 应用程序注册中获得的应用程序客户端 ID。
    权限
  • authority:授权 URL 指定了身份验证和授权的发生位置。对于 Azure AD 消费者账户,请使用 “https://login.microsoftonline.com/consumers”。
  • redirectURI:这是用户身份验证成功后将重定向到的 URI。应根据应用程序的设置进行配置。
const pubClientApp = new PublicClientApplication({
auth: {
clientId: "Paste your client ID",
authority: "https://login.microsoftonline.com/consumers",
redirectUri: "/",
},
});

注:为确保最佳性能,必须在组件树之外实例化 pubClientApp 对象。这样可以避免在组件重新渲染时重新创建该对象,从而降低效率。把它放在组件外,就能保证只创建一次,并在需要时重复使用。

  1. pubClientApp 对象作为 prop 传递给应用程序组件:现在,将 pubClientApp 对象提供给应用程序组件。通常的做法是将其作为 prop 传递给组件,这样应用程序就能无缝地管理身份验证和授权。
<App msalInstance={pubClientApp}/>

初始化 MSAL 提供程序

要使应用程序中的组件能够访问身份验证状态,应将它们封装在 MsalProvider 组件中。请按照以下步骤在 App.js 文件中进行设置:

  • 首先,从 msal-react 库中导入 MsalProvider 组件。
import { MsalProvider } from "@azure/msal-react";
  • MsalProvider 封装应用程序组件。提供 msalInstance 属性并将配置好的应用程序实例传递给 MsalProvider
function App({ msalInstance }) {
return (
<MsalProvider instance={msalInstance}>
<PageLayout>
<Grid container justifyContent="center">
<Pages />
</Grid>
</PageLayout>
</MsalProvider>
);
}

通过用 MsalProvider 封装组件,应用程序可以访问 msal-react 上下文。该上下文提供了对身份验证相关功能的访问,使在 React 应用程序中实施安全身份验证和授权变得更容易。

创建登录组件

要为应用程序创建登录组件,请按照 SignInButton.jsx 文件中的以下步骤操作:

  • 首先导入 useMsal 钩子以访问 MSAL 实例。
import { useMsal } from '@azure/msal-react';
  • 利用 useMsal 钩子访问  MSAL instance。使用钩子创建名为 instance 的变量,从而访问先前配置的  MSAL instance
const { instance } = useMsal();
  • 使用 instance.loginPopup() 方法定义 “handleLogin” 函数。该函数通过弹出窗口提示用户使用用户名和密码登录
const handleLogin = () => {
instance.loginPopup();
};
  • 在用户首次登录时,通过指定 scopes 等选项来请求权限,从而定制登录体验。
const handleLogin = () => {
instance.loginPopup({
scopes: ["user.read"],
});
};
  • 为组件中的按钮添加调用 handleLogin 函数的 onClick 属性。
<Button color="inherit" onClick={handleLogin}>
Sign in
</Button>

以下是 SignInButton.jsx 文件,供参考:

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>
);
};

创建签出组件

创建签出组件的方法与创建签入组件类似。请在 SignOutButton.jsx 文件中按照以下步骤创建签出组件:

  • msal-react 中导入 useMsal 钩子,以访问用于处理签出的  MSAL instance
import { useMsal } from '@azure/msal-react';
  • 利用 useMsal 钩子访问 MSAL instance。使用 useMsal 钩子创建一个名为 instance 的变量。这样就可以访问为应用程序配置的  MSAL instance
const { instance } = useMsal();
  • 定义 handleLogout 函数,该函数使用 instance.logoutPopup() 方法。该函数会触发一个用于注销用户的弹出窗口,并在注销后将用户重定向到主页。
const handleLogout = () => {
instance.logoutPopup();
};
  • handleLogout 功能整合到组件中按钮的 onClick 属性中。
<Button color="inherit" onClick={handleLogout}>
Sign out
</Button>;

下面是 SignOutButton.jsx 文件,供参考:

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>
);
};

根据身份验证状态有条件地渲染 UI 元素

要根据用户的身份验证状态有条件地在应用程序中呈现 UI 元素,请在 NavBar.jsxHome.jsx 文件中按照以下步骤操作。

  • NavBar.jsx 文件中

msal-react 导入 useIsAuthenticated 钩子。此钩子允许根据用户的身份验证状态有条件地呈现元素。

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

根据用户的身份验证状态,有条件地在组件中呈现 WelcomeName 元素。

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

根据用户的身份验证状态有条件地渲染 SignInButtonSignOutButton 元素。如果已通过身份验证,则渲染 SignOutButton ,如果未通过身份验证,则渲染 SignInButton

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

利用 msal-react 提供的 AuthenticatedTemplateUnauthenticatedTemplate 组件实现条件文本呈现。从 msal-react 导入 AuthenticatedTemplateUnauthenticatedTemplate

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

AuthenticatedTemplate 中包围包含文本的 Typography 元素,这些文本将在用户登录时可见。

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

UnauthenticatedTemplate 中封装用户签出时应可见的 Typography 元素。

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

下面是这款应用程序的预览,它可以根据你的登录状态有选择性地显示特定信息。

根据你的登录状态有选择性地显示特定信息

使用 Tokens

我们将深入探讨获取访问 tokens 和发出经过验证的 API 请求的实际步骤。访问 tokens 是安全访问外部资源的关键,我们将探讨如何在应用程序中有效使用它们。

获取访问 tokens

要获取访问 tokens 以发出经过验证的 API 请求,请按照 Profile.jsx 文件中的以下步骤操作:

  • Profile.jsx 文件开头导入必要的依赖项。这些依赖项是处理身份验证和获取访问 tokens 所必需的。
import { useMsalAuthentication } from "@azure/msal-react";
import { InteractionType } from "@azure/msal-browser";
import { useEffect, useState } from "react";
  • 使用 useState 钩子创建名为 displayData 的状态变量。该状态变量将存储从已验证的应用程序接口获取的数据
const [displayData, setDisplayData] = useState(null);
  • 利用 useMsalAuthentication 钩子获取访问 tokens。该钩子需要两个参数:interaction type 和指定请求 scopes 的对象。
const { result, error } = useMsalAuthentication(InteractionType.Redirect, {
scopes: ["user.read"],
});

要处理访问 token 并执行相关操作,请使用 useEffect 钩子。当组件挂载时,应运行此效果。在此效果中,您可以运行一系列检查:

  • 检查 displayData 是否存在,以防止在数据已经可用的情况下不必要地重新执行效果。
  • 检查是否存在任何身份验证错误,并将其记录到控制台以进行错误处理。
  • 检查 result 是否存在,然后提取访问 token。
useEffect(() => {
if (!displayData) {
return;
}
if (error) {
console.log(error);
return;
}
if (result) {
const accessToken = result.accessToken;
}
}, [displayData, error, result]);

执行通过身份验证的 API 请求

要在 React 应用程序中发出通过身份验证的 API 请求并处理响应,请按照以下步骤操作:

在 src 文件夹中创建名为 Fetch.js 的新文件,以封装用于发出 API 请求的函数。

  • Fetch.js 文件中:

定义一个名为 retrieveData 的函数,将 endpoint 和 access token 作为参数。该函数将处理 API 请求。

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

retrieveData 函数中,创建 Headers 对象,并用访问 token(bearer token)设置授权标题。

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

创建一个包含 HTTP method 和 headersoptions 对象。

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

使用 fetch 函数发出 API 请求。使用 .then() 处理响应,并使用 .catch() 捕捉任何错误。

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

下面是 Fetch.js 文件,供参考:

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));
};
  • Profile.jsx 中:

Fetch.js 导入 retrieveData 函数。这将为您的配置文件组件做好准备,以便利用该功能发出经过身份验证的 API 请求。

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

使用 retrieveData 方法发出经过验证的 API 请求。例如,可以将 endpoint 设置为 “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));
}

在组件的 return 语句中,如果存在数据,则渲染数据( displayData );否则,不显示任何数据。

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

下面是 Profile.jsx 文件,供参考:

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
};

下面是该应用的预览版,展示了用户登录时的具体细节。

用户登录时的具体细节

显示已登录用户的名称

要在用户界面中显示已登录用户的姓名以增强用户体验,请按以下步骤操作:

  • index.js 文件:

导入必要的依赖项并设置 MSAL 事件回调以处理成功登录事件。

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);
}
});
  • WelcomeName.jsx 组件中:

导入访问应用程序实例和管理组件状态所需的依赖项。

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

定义状态变量 username ,用于存储登录用户的用户名。

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

使用 useMsal 钩子访问之前创建的应用程序实例( instance )。

const { instance } = useMsal();

useEffect 钩子中,将 currentAccount 设置为活动账户,并更新 username 状态变量。确保在依赖关系数组中包含 instance,以观察变化。

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

在组件的 return 语句中,使用 Typography 组件在用户界面中显示用户名。

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

登录时显示用户名的应用程序快照:

登录时显示用户名的应用程序快照

错误处理

要在应用程序中使用 MSAL 启用错误处理和日志记录功能,请按照以下步骤操作:

打开 index.js 文件:

  • pubClientApp 对象中,包含一个 cache 对象,其中包含 cacheLocationstoreAuthStateInCookie 等选项。这些选项有助于控制验证工件的缓存和管理方式。
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false, 
}
  • pubClientApp 对象中包含一个 system 对象,并为日志配置定义 loggerOptions。这样就可以指定 MSAL 处理日志的方式。
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).
}
}

有了这些配置, MSAL 就会在控制台中记录交互、错误和其他信息。您可以使用这些信息来调试和监控身份验证过程。

请注意,此设置可帮助您调试和监控与身份验证相关的活动,并排除用户与 Azure AD 交互过程中可能出现的任何问题。

记录交互的浏览器控制台预览。

记录交互的浏览器控制台预览

处理身份验证错误

要在 MSAL 应用程序中处理身份验证错误和索赔挑战,请按照以下步骤操作:

  • index.js 文件中

auth 对象中添加 clientCapabilities 选项。该选项声明应用程序能够处理索赔挑战。

auth: {
clientId: "5d804fed-8b0e-4c9b-b949-6020d4945ead",
authority: "https://login.microsoftonline.com/consumers",
redirectUri: "http://localhost:3000/",
clientCapabilities: ['CP1']
},
  • Fetch.js 文件中:添加一个名为 handleClaims 的函数,用于检查响应的状态代码。
    • 如果状态为 200(表示成功),则返回 JSON 格式的响应。
      如果状态为 401,它会检查响应头是否包含 “authenticated”。如果包含,则会从标头中提取 claimsChallenge 并将其存储到 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.
}
};

修改 fetch 调用,通过 handleClaims 函数传递 response 。这可确保对响应进行处理,以处理索赔质疑或其他错误。

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

useMsalAuthentication 配置中添加一个参数 claims。该参数设置为存储在 sessionStorage 中的 claimsChallenge

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

通过这些步骤,您可以在 MSAL 应用程序中处理身份验证错误和理赔挑战,使其更加强大,并能够在身份验证过程中管理自定义理赔挑战。

这有一份关于解决 Microsoft Entra ID 问题的故障排除指南

小结

本综合指南提供了使用 MSAL-React 实施身份验证的分步指南。它帮助读者建立开发环境、配置 MSAL-React 、创建签入和签出组件、提出经过验证的 API 请求,以及有效处理身份验证错误。

评论留言