使用微軟MSAL庫在React中進行身份驗證

使用微軟MSAL庫在React中進行身份驗證

在當今的應用程式中,身份驗證是強制性的,而微軟的身份驗證庫(MSAL)是解決這一問題的有力方案。本文將向您展示在您自己的 React 程式碼中實現 MSAL 的所有細節。

在當今的數字環境中,確保使用者資料的安全和隱私至關重要。無論您是在構建 Web 應用程式、移動應用程式還是其他任何需要使用者身份驗證的軟體,Microsoft 的 React Authentication LibraryMSAL-React )都能提供一個強大的解決方案來簡化這一過程。通過 MSAL-React,開發人員可以將安全身份驗證無縫整合到他們的應用程式中,為使用者提供安全、友好的使用體驗。

在本綜合指南中,我們將一步一步地指導您使用 MSAL-React 實施身份驗證,幫助您利用微軟身份驗證平臺的強大功能來保護使用者資料並提高應用程式的可信度。無論您是經驗豐富的開發人員還是剛剛開始工作,本文都將為您提供相關知識和工具,幫助您在 React 應用程式中採用強大的身份驗證功能。

設定開發環境

構建應用程式所需的軟體和工具有:

  • Node.jsnpm:從官方網站安裝 Node.js,其中包括 npmnpm 用於管理依賴關係和執行指令碼。
  • 複製模板: 該模板已構建了單頁前端。下面是複製的方法:
    • 首先,fork 倉庫
      開啟終端,在終端中執行此命令克隆版本庫。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
git clone <your_repository_url>
git clone <your_repository_url>
git clone <your_repository_url>

在終端中使用此命令導航到專案目錄。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cd msal-react-demo-template
cd msal-react-demo-template
cd msal-react-demo-template

導航到應用程式目錄後,安裝依賴項。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install
npm install
npm install

啟動應用程式。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm start
npm start
npm start

下面是該程式使用者介面的初始外觀。

使用者介面的初始外觀

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install --save @azure/msal-browser @azure/msal-react
npm install --save @azure/msal-browser @azure/msal-react
npm install --save @azure/msal-browser @azure/msal-react

專案結構如下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
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 身份驗證和授權的庫。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { PublicClientApplication } from '@azure/msal-browser';
import { PublicClientApplication } from '@azure/msal-browser';
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。應根據應用程式的設定進行配置。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const pubClientApp = new PublicClientApplication({
auth: {
clientId: "Paste your client ID",
authority: "https://login.microsoftonline.com/consumers",
redirectUri: "/",
},
});
const pubClientApp = new PublicClientApplication({ auth: { clientId: "Paste your client ID", authority: "https://login.microsoftonline.com/consumers", redirectUri: "/", }, });
const pubClientApp = new PublicClientApplication({
auth: {
clientId: "Paste your client ID",
authority: "https://login.microsoftonline.com/consumers",
redirectUri: "/",
},
});

注:為確保最佳效能,必須在元件樹之外例項化 pubClientApp 物件。這樣可以避免在元件重新渲染時重新建立該物件,從而降低效率。把它放在元件外,就能保證只建立一次,並在需要時重複使用。

  1. pubClientApp 物件作為 prop 傳遞給應用程式元件:現在,將 pubClientApp 物件提供給應用程式元件。通常的做法是將其作為 prop 傳遞給元件,這樣應用程式就能無縫地管理身份驗證和授權。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<App msalInstance={pubClientApp}/>
<App msalInstance={pubClientApp}/>
<App msalInstance={pubClientApp}/>

初始化 MSAL 提供程式

要使應用程式中的元件能夠訪問身份驗證狀態,應將它們封裝在 MsalProvider 元件中。請按照以下步驟在 App.js 檔案中進行設定:

  • 首先,從 msal-react 庫中匯入 MsalProvider 元件。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { MsalProvider } from "@azure/msal-react";
import { MsalProvider } from "@azure/msal-react";
import { MsalProvider } from "@azure/msal-react";
  • MsalProvider 封裝應用程式元件。提供 msalInstance 屬性並將配置好的應用程式例項傳遞給 MsalProvider
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function App({ msalInstance }) {
return (
<MsalProvider instance={msalInstance}>
<PageLayout>
<Grid container justifyContent="center">
<Pages />
</Grid>
</PageLayout>
</MsalProvider>
);
}
function App({ msalInstance }) { return ( <MsalProvider instance={msalInstance}> <PageLayout> <Grid container justifyContent="center"> <Pages /> </Grid> </PageLayout> </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 例項。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useMsal } from '@azure/msal-react';
import { useMsal } from '@azure/msal-react';
import { useMsal } from '@azure/msal-react';
  • 利用 useMsal 鉤子訪問  MSAL instance。使用鉤子建立名為 instance 的變數,從而訪問先前配置的  MSAL instance
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const { instance } = useMsal();
const { instance } = useMsal();
const { instance } = useMsal();
  • 使用 instance.loginPopup() 方法定義 “handleLogin” 函式。該函式通過彈出視窗提示使用者使用使用者名稱和密碼登入
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const handleLogin = () => {
instance.loginPopup();
};
const handleLogin = () => { instance.loginPopup(); };
const handleLogin = () => {
instance.loginPopup();
};
  • 在使用者首次登入時,通過指定 scopes 等選項來請求許可權,從而定製登入體驗。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const handleLogin = () => {
instance.loginPopup({
scopes: ["user.read"],
});
};
const handleLogin = () => { instance.loginPopup({ scopes: ["user.read"], }); };
const handleLogin = () => {
instance.loginPopup({
scopes: ["user.read"],
});
};
  • 為元件中的按鈕新增呼叫 handleLogin 函式的 onClick 屬性。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<Button color="inherit" onClick={handleLogin}>
Sign in
</Button>
<Button color="inherit" onClick={handleLogin}> Sign in </Button>
<Button color="inherit" onClick={handleLogin}>
Sign in
</Button>

以下是 SignInButton.jsx 檔案,供參考:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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>
);
};
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> ); };
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
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useMsal } from '@azure/msal-react';
import { useMsal } from '@azure/msal-react';
import { useMsal } from '@azure/msal-react';
  • 利用 useMsal 鉤子訪問 MSAL instance。使用 useMsal 鉤子建立一個名為 instance 的變數。這樣就可以訪問為應用程式配置的  MSAL instance
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const { instance } = useMsal();
const { instance } = useMsal();
const { instance } = useMsal();
  • 定義 handleLogout 函式,該函式使用 instance.logoutPopup() 方法。該函式會觸發一個用於登出使用者的彈出視窗,並在登出後將使用者重定向到主頁。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const handleLogout = () => {
instance.logoutPopup();
};
const handleLogout = () => { instance.logoutPopup(); };
const handleLogout = () => {
instance.logoutPopup();
};
  • handleLogout 功能整合到元件中按鈕的 onClick 屬性中。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<Button color="inherit" onClick={handleLogout}>
Sign out
</Button>;
<Button color="inherit" onClick={handleLogout}> Sign out </Button>;
<Button color="inherit" onClick={handleLogout}>
Sign out
</Button>;

下面是 SignOutButton.jsx 檔案,供參考:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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>
);
};
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> ); };
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 鉤子。此鉤子允許根據使用者的身份驗證狀態有條件地呈現元素。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useIsAuthenticated } from "@azure/msal-react";
import { useIsAuthenticated } from "@azure/msal-react";
import { useIsAuthenticated } from "@azure/msal-react";

根據使用者的身份驗證狀態,有條件地在元件中呈現 WelcomeName 元素。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Conditional rendering: Display the WelcomeName component only if isAuthenticated is true.
{
isAuthenticated ? <WelcomeName /> : null;
}
// Conditional rendering: Display the WelcomeName component only if isAuthenticated is true. { isAuthenticated ? <WelcomeName /> : null; }
// Conditional rendering: Display the WelcomeName component only if isAuthenticated is true.
{
isAuthenticated ? <WelcomeName /> : null;
}

根據使用者的身份驗證狀態有條件地渲染 SignInButtonSignOutButton 元素。如果已通過身份驗證,則渲染 SignOutButton ,如果未通過身份驗證,則渲染 SignInButton

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Display the SignOutButton if isAuthenticated is true, otherwise display the SignInButton.
{
isAuthenticated ? <SignOutButton /> : <SignInButton />;
}
// Display the SignOutButton if isAuthenticated is true, otherwise display the SignInButton. { isAuthenticated ? <SignOutButton /> : <SignInButton />; }
// Display the SignOutButton if isAuthenticated is true, otherwise display the SignInButton.
{
isAuthenticated ? <SignOutButton /> : <SignInButton />;
}
  • Home.jsx 檔案中:

利用 msal-react 提供的 AuthenticatedTemplateUnauthenticatedTemplate 元件實現條件文字呈現。從 msal-react 匯入 AuthenticatedTemplateUnauthenticatedTemplate

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { AuthenticatedTemplate, UnauthenticatedTemplate } from "@azure/msal-react"
import { AuthenticatedTemplate, UnauthenticatedTemplate } from "@azure/msal-react"
import { AuthenticatedTemplate, UnauthenticatedTemplate } from "@azure/msal-react"

AuthenticatedTemplate 中包圍包含文字的 Typography 元素,這些文字將在使用者登入時可見。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<AuthenticatedTemplate>
<Typography variant="h6">
You are signed-in. Select profile to call Microsoft Graph.
</Typography>
</AuthenticatedTemplate>;
<AuthenticatedTemplate> <Typography variant="h6"> You are signed-in. Select profile to call Microsoft Graph. </Typography> </AuthenticatedTemplate>;
<AuthenticatedTemplate>
<Typography variant="h6">
You are signed-in. Select profile to call Microsoft Graph.
</Typography>
</AuthenticatedTemplate>;

UnauthenticatedTemplate 中封裝使用者簽出時應可見的 Typography 元素。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<UnauthenticatedTemplate>
<Typography variant="h6">
Please sign in to see your profile information.
</Typography>
</UnauthenticatedTemplate>;
<UnauthenticatedTemplate> <Typography variant="h6"> Please sign in to see your profile information. </Typography> </UnauthenticatedTemplate>;
<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 所必需的。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useMsalAuthentication } from "@azure/msal-react";
import { InteractionType } from "@azure/msal-browser";
import { useEffect, useState } from "react";
import { useMsalAuthentication } from "@azure/msal-react"; import { InteractionType } from "@azure/msal-browser"; import { useEffect, useState } from "react";
import { useMsalAuthentication } from "@azure/msal-react";
import { InteractionType } from "@azure/msal-browser";
import { useEffect, useState } from "react";
  • 使用 useState 鉤子建立名為 displayData 的狀態變數。該狀態變數將儲存從已驗證的應用程式介面獲取的資料
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const [displayData, setDisplayData] = useState(null);
const [displayData, setDisplayData] = useState(null);
const [displayData, setDisplayData] = useState(null);
  • 利用 useMsalAuthentication 鉤子獲取訪問 tokens。該鉤子需要兩個引數:interaction type 和指定請求 scopes 的物件。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const { result, error } = useMsalAuthentication(InteractionType.Redirect, {
scopes: ["user.read"],
});
const { result, error } = useMsalAuthentication(InteractionType.Redirect, { scopes: ["user.read"], });
const { result, error } = useMsalAuthentication(InteractionType.Redirect, {
scopes: ["user.read"],
});

要處理訪問 token 並執行相關操作,請使用 useEffect 鉤子。當元件掛載時,應執行此效果。在此效果中,您可以執行一系列檢查:

  • 檢查 displayData 是否存在,以防止在資料已經可用的情況下不必要地重新執行效果。
  • 檢查是否存在任何身份驗證錯誤,並將其記錄到控制檯以進行錯誤處理。
  • 檢查 result 是否存在,然後提取訪問 token。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
useEffect(() => {
if (!displayData) {
return;
}
if (error) {
console.log(error);
return;
}
if (result) {
const accessToken = result.accessToken;
}
}, [displayData, error, result]);
useEffect(() => { if (!displayData) { return; } if (error) { console.log(error); return; } if (result) { const accessToken = result.accessToken; } }, [displayData, error, result]);
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 請求。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
export const retrieveData = (endpoint, accessToken) => {};
export const retrieveData = (endpoint, accessToken) => {};
export const retrieveData = (endpoint, accessToken) => {};

retrieveData 函式中,建立 Headers 物件,並用訪問 token(bearer token)設定授權標題。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const headers = new Headers();
const bearer = `Bearer ${accessToken}`;
headers.append("Authorization", bearer);
const headers = new Headers(); const bearer = `Bearer ${accessToken}`; headers.append("Authorization", bearer);
const headers = new Headers();
const bearer = `Bearer ${accessToken}`;
headers.append("Authorization", bearer);

建立一個包含 HTTP method 和 headersoptions 物件。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const options = {
method: "GET",
headers: headers,
};
const options = { method: "GET", headers: headers, };
const options = {
method: "GET",
headers: headers,
};

使用 fetch 函式發出 API 請求。使用 .then() 處理響應,並使用 .catch() 捕捉任何錯誤。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return fetch(endpoint, options)
.then((response) => response.json())
.catch((error) => console.log(error));
return fetch(endpoint, options) .then((response) => response.json()) .catch((error) => console.log(error));
return fetch(endpoint, options)
.then((response) => response.json())
.catch((error) => console.log(error));

下面是 Fetch.js 檔案,供參考:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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));
};
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)); };
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 請求。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { retrieveData } from "../Fetch";
import { retrieveData } from "../Fetch";
import { retrieveData } from "../Fetch";

使用 retrieveData 方法發出經過驗證的 API 請求。例如,可以將 endpoint 設定為 “https://graph.microsoft.com/v1.0/me”。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
if (result) {
const accessToken = result.accessToken;
retrieveData("https://graph.microsoft.com/v1.0/me", accessToken)
.then((response) => setDisplayData(response))
.catch((error) => console.log(error));
}
if (result) { const accessToken = result.accessToken; retrieveData("https://graph.microsoft.com/v1.0/me", accessToken) .then((response) => setDisplayData(response)) .catch((error) => console.log(error)); }
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 );否則,不顯示任何資料。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return <>{displayData ? <ProfileData displayData={displayData} /> : null}</>;
return <>{displayData ? <ProfileData displayData={displayData} /> : null}</>;
return <>{displayData ? <ProfileData displayData={displayData} /> : null}</>;

下面是 Profile.jsx 檔案,供參考:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
};
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 };
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 事件回撥以處理成功登入事件。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
}
});
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); } });
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 元件中:

匯入訪問應用程式例項和管理元件狀態所需的依賴項。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useMsal } from "@azure/msal-react";
import { useState, useEffect } from "react";
import { useMsal } from "@azure/msal-react"; import { useState, useEffect } from "react";
import { useMsal } from "@azure/msal-react";
import { useState, useEffect } from "react";

定義狀態變數 username ,用於儲存登入使用者的使用者名稱。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const [username, setUsername] = useState('');
const [username, setUsername] = useState('');
const [username, setUsername] = useState('');

使用 useMsal 鉤子訪問之前建立的應用程式例項( instance )。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const { instance } = useMsal();
const { instance } = useMsal();
const { instance } = useMsal();

useEffect 鉤子中,將 currentAccount 設定為活動賬戶,並更新 username 狀態變數。確保在依賴關係陣列中包含 instance,以觀察變化。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
useEffect(() => {
const currentAccount = instance.getActiveAccount();
if (currentAccount) {
setUsername(currentAccount.username);
}
}, [instance]);
useEffect(() => { const currentAccount = instance.getActiveAccount(); if (currentAccount) { setUsername(currentAccount.username); } }, [instance]);
useEffect(() => {
const currentAccount = instance.getActiveAccount();
if (currentAccount) {
setUsername(currentAccount.username);
}
}, [instance]);

在元件的 return 語句中,使用 Typography 元件在使用者介面中顯示使用者名稱。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return <Typography variant="h6">Welcome, {username}</Typography>;
return <Typography variant="h6">Welcome, {username}</Typography>;
return <Typography variant="h6">Welcome, {username}</Typography>;

登入時顯示使用者名稱的應用程式快照:

登入時顯示使用者名稱的應用程式快照

錯誤處理

要在應用程式中使用 MSAL 啟用錯誤處理和日誌記錄功能,請按照以下步驟操作:

開啟 index.js 檔案:

  • pubClientApp 物件中,包含一個 cache 物件,其中包含 cacheLocationstoreAuthStateInCookie 等選項。這些選項有助於控制驗證工件的快取和管理方式。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false,
}
cache: { cacheLocation: 'localStorage', storeAuthStateInCookie: false, }
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false, 
}
  • pubClientApp 物件中包含一個 system 物件,併為日誌配置定義 loggerOptions。這樣就可以指定 MSAL 處理日誌的方式。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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).
}
}
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). } }
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 選項。該選項宣告應用程式能夠處理索賠挑戰。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
auth: {
clientId: "5d804fed-8b0e-4c9b-b949-6020d4945ead",
authority: "https://login.microsoftonline.com/consumers",
redirectUri: "http://localhost:3000/",
clientCapabilities: ['CP1']
},
auth: { clientId: "5d804fed-8b0e-4c9b-b949-6020d4945ead", authority: "https://login.microsoftonline.com/consumers", redirectUri: "http://localhost:3000/", clientCapabilities: ['CP1'] },
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 中。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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.
}
};
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. } };
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 。這可確保對響應進行處理,以處理索賠質疑或其他錯誤。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return fetch(endpoint, options)
.then((response) => handleClaims(response))
.catch((error) => console.log(error));
return fetch(endpoint, options) .then((response) => handleClaims(response)) .catch((error) => console.log(error));
return fetch(endpoint, options)
.then((response) => handleClaims(response))
.catch((error) => console.log(error));
  • Profile.jsx 元件中:

useMsalAuthentication 配置中新增一個引數 claims。該引數設定為儲存在 sessionStorage 中的 claimsChallenge

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const { result, error } = useMsalAuthentication(InteractionType.Redirect, {
scopes: ["user.read"],
claims: sessionStorage.getItem("claimsChallenge")
? window.atob(sessionStorage.getItem("claimsChallenge"))
: undefined,
});
const { result, error } = useMsalAuthentication(InteractionType.Redirect, { scopes: ["user.read"], claims: sessionStorage.getItem("claimsChallenge") ? window.atob(sessionStorage.getItem("claimsChallenge")) : undefined, });
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 請求,以及有效處理身份驗證錯誤。

評論留言