MSAL React 入门

本文将指导你了解如何开始使用 @azure/msal-react。 我们将介绍初始化、判断用户是否已经过身份验证、保护组件、让用户登录并获取访问令牌。

先决条件

初始 化

@azure/msal-react 是基于 React 上下文 API 构建的,需要身份验证的应用的所有部分都必须包装在 MsalProvider 组件中。 首先,您需要初始化PublicClientApplication的一个实例,然后将该实例作为 prop 传递给MsalProvider

import React from "react";
import { createRoot } from "react-dom/client";

import { MsalProvider } from "@azure/msal-react";
import { Configuration,  PublicClientApplication } from "@azure/msal-browser";

import App from "./app.jsx";

// MSAL configuration
const configuration: Configuration = {
    auth: {
        clientId: "client-id"
    }
};

const pca = new PublicClientApplication(configuration);

// Component
const AppProvider = () => (
    <MsalProvider instance={pca}>
        <App />
    </MsalProvider>
);

const root = createRoot(document.getElementById("root"));
root.render(<AppProvider />);

所有MsalProvider下的组件将通过上下文访问PublicClientApplication实例,并且所有由@azure/msal-react提供的挂钩和组件也可供使用。

确定是否对用户进行身份验证

大多数应用程序都需要根据用户是否登录来有条件地呈现某些组件。 @azure/msal-react 提供了 2 种简单的方法来执行此操作。

AuthenticatedTemplateUnauthenticatedTemplate

AuthenticatedTemplateUnauthenticatedTemplate 组件将分别仅在用户通过身份验证或未通过身份验证时渲染其子项。

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

export function App() {
    return (
        <React.Fragment>
            <p>Anyone can see this paragraph.</p>
            <AuthenticatedTemplate>
                <p>At least one account is signed in!</p>
            </AuthenticatedTemplate>
            <UnauthenticatedTemplate>
                <p>No users are signed in!</p>
            </UnauthenticatedTemplate>
        </React.Fragment>
    );
}

useIsAuthenticated 挂钩

作为上述包装组件的替代方案,你的应用可以使用 useIsAuthenticated 钩子。 可以在 MSAL React 中的 Hooks 中阅读有关此内容的详细信息。

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

export function App() {
    const isAuthenticated = useIsAuthenticated();

    return (
        <React.Fragment>
            <p>Anyone can see this paragraph.</p>
            {isAuthenticated && (
                <p>At least one account is signed in!</p>
            )}
            {!isAuthenticated && (
                <p>No users are signed in!</p>
            )}
        </React.Fragment>
    );
}

保护组件

如果您有一些组件只想向已通过身份验证的用户显示,则可以使用上述任何一种方法。 但是,如果要在用户尚未进行身份验证的情况下自动调用登录名,该怎么办? @azure/msal-react提供了两种实现此操作的方式:使用MsalAuthenticationTemplateuseMsalAuthentication钩子。

MsalAuthenticationTemplate 组件

MsalAuthenticationTemplate 组件将在用户通过身份验证时渲染其子项,或尝试让用户登录。 只需向其提供要使用的交互类型(重定向或弹出窗口),并可以选择将 请求对象 传递给登录 API、身份验证正在进行时显示的组件或发生错误时要显示的组件。

可以在页面上的任何示例/profile中找到此示例。

import React from "react";
import { MsalAuthenticationTemplate } from "@azure/msal-react";
import { InteractionType } from "@azure/msal-browser";

function ErrorComponent({error}) {
    return <p>An Error Occurred: {error}</p>;
}

function LoadingComponent() {
    return <p>Authentication in progress...</p>;
}

export function Example() {
    const authRequest = {
        scopes: ["openid", "profile"]
    };

    return (
        // authenticationRequest, errorComponent and loadingComponent props are optional
        <MsalAuthenticationTemplate 
            interactionType={InteractionType.Popup} 
            authenticationRequest={authRequest} 
            errorComponent={ErrorComponent} 
            loadingComponent={LoadingComponent}
        >
            <p>At least one account is signed in!</p>
        </MsalAuthenticationTemplate>
      )
};

useMsalAuthentication 挂钩

useMsalAuthentication 钩子会先检查是否有用户已登录,如果当前没有用户登录,则尝试登录一个用户。 需要提供要使用的交互类型(重定向或弹出窗口)。 它将返回登录操作的结果、发生的任何错误,以及如果需要重试,可以使用的登录函数。

您可以在hooks 文档中了解有关此钩子的更多信息。

import React from 'react';
import { useMsalAuthentication } from "@azure/msal-react";
import { InteractionType } from '@azure/msal-browser';

export function App() {
    const {login, result, error} = useMsalAuthentication(InteractionType.Popup);

    return (
        <React.Fragment>
            <p>Anyone can see this paragraph.</p>
            <AuthenticatedTemplate>
                <p>At least one account is signed in!</p>
            </AuthenticatedTemplate>
            <UnauthenticatedTemplate>
                <p>No users are signed in!</p>
            </UnauthenticatedTemplate>
        </React.Fragment>
    );
}

使用 @azure/msal-browser 提供的登录 API 让用户登录

另一种调用登录的方法是直接使用 PublicClientApplication 实例中的 @azure/msal-browser API。 可通过 3 种方法从上下文访问实例。

useMsal 挂钩

一个钩子,返回 PublicClientApplication 实例、当前登录的所有帐户的数组以及一个告诉你 msal 当前正在执行的 inProgress 值。

你可以在 Hooks 文档中阅读有关此 Hook 的更多内容。

import React from 'react';
import { useMsal } from "@azure/msal-react";

export function App() {
    const { instance, accounts, inProgress } = useMsal();

    if (accounts.length > 0) {
        return <span>There are currently {accounts.length} users signed in!</span>
    } else if (inProgress === "login") {
        return <span>Login is currently in progress!</span>
    } else {
        return (
            <>
                <span>There are currently no users signed in!</span>
                <button onClick={() => instance.loginPopup()}>Login</button>
            </>
        );
    }
}

使用原始上下文

如果你使用的是类组件且不能使用钩子,你可以通过 MsalContext 使用原始 msal 上下文。 你可以在此处阅读更多关于在类组件中使用@azure/msal-react的信息。

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

class App extends React.Component {
    static contextType = MsalContext;

    render() {
        if (this.context.accounts.length > 0) {
            return <span>There are currently {this.context.accounts.length} users signed in!</span>
        } else if (this.context.inProgress === "login") {
            return <span>Login is currently in progress!</span>
        } else {
            return (
                <>
                    <span>There are currently no users signed in!</span>
                    <button onClick={() => this.context.instance.loginPopup()}>Login</button>
                </>
            );
        }
    }
}

使用 withMsal 高阶组件将你的组件包装起来

在类组件和函数组件中消费 MSAL 上下文的另一种方法是用 withMsal HOC 包装你的组件,这将把上下文注入到组件的 props 中。

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

class LoginButton extends React.Component {
    render() {
        const isAuthenticated = this.props.msalContext.accounts.length > 0;
        const msalInstance = this.props.msalContext.instance;
        if (isAuthenticated) {
            return <button onClick={() => msalInstance.logout()}>Logout</button>    
        } else {
            return <button onClick={() => msalInstance.loginPopup()}>Login</button>
        }
    }
}

export default YourWrappedComponent = withMsal(LoginButton);

获取访问令牌

建议您的应用在每次需要使用访问令牌来访问 API 时,都调用您的 PublicClientApplication 对象上的 acquireTokenSilent API。 这可以类似于上一部分所述的方法。

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

export function App() {
    const { instance, accounts, inProgress } = useMsal();
    const account = useAccount(accounts[0] || {});
    const [apiData, setApiData] = useState(null);

    useEffect(() => {
        if (account) {
            instance.acquireTokenSilent({
                scopes: ["User.Read"],
                account: account
            }).then((response) => {
                if(response) {
                    callMsGraph(response.accessToken).then((result) => setApiData(result));
                }
            });
        }
    }, [account, instance]);

    if (accounts.length > 0) {
        return (
            <>
                <span>There are currently {accounts.length} users signed in!</span>
                {apiData && (<span>Data retreived from API: {JSON.stringify(apiData)}</span>)}
            </>
        );
    } else if (inProgress === "login") {
        return <span>Login is currently in progress!</span>
    } else {
        return <span>There are currently no users signed in!</span>
    }
}

获取 React 组件外部的访问令牌

如果你需要在 React 组件外部获取访问令牌,可以直接在 PublicClientApplication 上调用 acquireTokenSilent 函数。 我们不建议在 MsalProvider 提供的 React 上下文之外调用会更改用户身份验证状态(登录、登出)的函数,因为该上下文中的组件可能无法正确更新。

请记住,必须先登录用户才能获取令牌。

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

// This should be the same instance you pass to MsalProvider
const msalInstance = new PublicClientApplication(config);

const acquireAccessToken = async (msalInstance) => {
    const activeAccount = msalInstance.getActiveAccount(); // This will only return a non-null value if you have logic somewhere else that calls the setActiveAccount API
    const accounts = msalInstance.getAllAccounts();

    if (!activeAccount && accounts.length === 0) {
        /*
        * User is not signed in. Throw error or wait for user to login.
        * Do not attempt to log a user in outside of the context of MsalProvider
        */   
    }
    const request = {
        scopes: ["User.Read"],
        account: activeAccount || accounts[0]
    };

    const authResult = await msalInstance.acquireTokenSilent(request);

    return authResult.accessToken
};

另见