教程:使用本机身份验证 JavaScript SDK 将用户登录到 React 单页应用

适用于绿色圆圈,带有白色复选标记符号,指示以下内容适用于外部租户。 外部租户(了解详细信息

本教程介绍如何使用本机身份验证 JavaScript SDK 将用户登录到 React 单页应用(SPA)。

在本教程中,你将:

  • 更新 React 应用以登录用户。
  • 测试登录流。

先决条件

创建 UI 组件

在本部分中,将创建收集用户登录信息的表单:

  1. 创建名为 src/app/sign-in 的文件夹。

  2. 创建 登录/组件/InitialForm.tsx 文件,然后粘贴 登录/components/InitialForm.tsx 中的代码。 此组件显示收集用户用户名(电子邮件)的窗体。

  3. 如果选择的身份验证方法是电子邮件和一次性密码,请创建 登录/组件/CodeForm.tsx 文件,然后粘贴 登录/组件/CodeForm.tsx 中的代码。 如果管理员在 Microsoft Entra 管理中心将电子邮件一次性密码设置为登录流,此组件将显示一个表单,用于从用户收集一次性密码。

  4. 如果选择的身份验证方法是电子邮件和密码,请创建 登录/组件/PasswordForm.tsx 文件,然后粘贴 登录/组件/PasswordForm.tsx 中的代码。 此组件显示收集用户密码的窗体。

  5. 创建 登录/组件/UserInfo.tsx 文件,然后粘贴 登录/组件/UserInfo.tsx 中的代码。 此组件显示已登录用户的用户名和登录状态。

处理表单交互

在本部分中,将添加用于处理登录表单交互的代码,例如启动登录流、提交用户密码或一次性密码。

创建 登录/page.tsx 文件以处理登录流的逻辑。 在此文件中:

  • 导入必要的组件,并根据状态显示正确的窗体。 请参阅 登录/page.tsx 中的完整示例:

    import {
      CustomAuthPublicClientApplication,
      ICustomAuthPublicClientApplication,
      SignInCodeRequiredState,
      // Uncommon if using a Email + Password flow
      // SignInPasswordRequiredState,
      SignInCompletedState,
      AuthFlowStateBase,
    } from "@azure/msal-browser/custom-auth";
    
    export default function SignIn() {
        const [authClient, setAuthClient] = useState<ICustomAuthPublicClientApplication | null>(null);
        const [username, setUsername] = useState("");
        //If you are sign in using a Email + Password flow, uncomment the following line
        //const [password, setPassword] = useState("");
        const [code, setCode] = useState("");
        const [error, setError] = useState("");
        const [loading, setLoading] = useState(false);
        const [signInState, setSignInState] = useState<AuthFlowStateBase | null>(null);
        const [data, setData] = useState<CustomAuthAccountData | undefined>(undefined);
        const [loadingAccountStatus, setLoadingAccountStatus] = useState(true);
        const [isSignedIn, setCurrentSignInStatus] = useState(false);
    
        useEffect(() => {
            const initializeApp = async () => {
                const appInstance = await CustomAuthPublicClientApplication.create(customAuthConfig);
                setAuthClient(appInstance);
            };
    
            initializeApp();
        }, []);
    
        useEffect(() => {
            const checkAccount = async () => {
                if (!authClient) return;
    
                const accountResult = authClient.getCurrentAccount();
    
                if (accountResult.isCompleted()) {
                    setCurrentSignInStatus(true);
                }
    
                setData(accountResult.data);
    
                setLoadingAccountStatus(false);
            };
    
            checkAccount();
        }, [authClient]);
    
        const renderForm = () => {
            if (loadingAccountStatus) {
                return;
            }
    
            if (isSignedIn || signInState instanceof SignInCompletedState) {
                return <UserInfo userData={data} />;
            }
            //If you are signing up using Email + Password flow, uncomment the following block of code
            /*
            if (signInState instanceof SignInPasswordRequiredState) {
                return (
                    <PasswordForm
                        onSubmit={handlePasswordSubmit}
                        password={password}
                        setPassword={setPassword}
                        loading={loading}
                    />
                );
            }
            */
            if (signInState instanceof SignInCodeRequiredState) {
                return <CodeForm onSubmit={handleCodeSubmit} code={code} setCode={setCode} loading={loading} />;
            }
    
            return <InitialForm onSubmit={startSignIn} username={username} setUsername={setUsername} loading={loading} />;
        };
    
        return (
            <div style={styles.container}>
                <h2 style={styles.h2}>Sign In</h2>
                <>
                    {renderForm()}
                    {error && <div style={styles.error}>{error}</div>}
                </>
            </div>
        );
    }
    
  • 若要启动登录流,请使用以下代码片段。 请参阅 登录/page.tsx 的完整示例,了解如何放置代码片段的位置:

    const startSignIn = async (e: React.FormEvent) => {
        e.preventDefault();
        setError("");
        setLoading(true);
    
        if (!authClient) return;
    
        // Start the sign-in flow
        const result = await authClient.signIn({
            username,
        });
    
        // Thge result may have the different states,
        // such as Password required state, OTP code rquired state, Failed state and Completed state.
    
        if (result.isFailed()) {
            if (result.error?.isUserNotFound()) {
                setError("User not found");
            } else if (result.error?.isInvalidUsername()) {
                setError("Username is invalid");
            } else if (result.error?.isPasswordIncorrect()) {
                setError("Password is invalid");
    
            } else {
                setError(`An error occurred: ${result.error?.errorData?.errorDescription}`);
            }
        }
    
        if (result.isCompleted()) {
            setData(result.data);
        }
    
        setSignInState(result.state);
    
        setLoading(false);
    };
    

    SDK 的实例方法 signIn()启动登录流。

  • 如果选择的身份验证流是电子邮件和一次性密码,请使用以下代码片段提交一次性密码。 请参阅 登录/page.tsx 的完整示例,了解如何放置代码片段的位置:

    const handleCodeSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        setError("");
        setLoading(true);
    
        if (signInState instanceof SignInCodeRequiredState) {
            const result = await signInState.submitCode(code);
    
            // the result object may have the different states, such as Failed state and Completed state.
    
            if (result.isFailed()) {
                if (result.error?.isInvalidCode()) {
                    setError("Invalid code");
                } else {
                    setError(result.error?.errorData?.errorDescription || "An error occurred while verifying the code");
                }
            }
    
            if (result.isCompleted()) {
                setData(result.data);
                setSignInState(result.state);
            }
        }
    
        setLoading(false);
    };
    

    登录状态 submitCode() 提交一次性密码。

  • 如果选择的身份验证流是电子邮件和密码,请使用以下代码片段提交用户的密码。 请参阅 登录/page.tsx 的完整示例,了解如何放置代码片段的位置:

    const handlePasswordSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        setError("");
        setLoading(true);
    
        if (signInState instanceof SignInPasswordRequiredState) {
            const result = await signInState.submitPassword(password);
    
            if (result.isFailed()) {
                if (result.error?.isInvalidPassword()) {
                    setError("Incorrect password");
                } else {
                    setError(
                        result.error?.errorData?.errorDescription || "An error occurred while verifying the password"
                    );
                }
            }
    
            if (result.isCompleted()) {
                setData(result.data);
    
                setSignInState(result.state);
            }
        }
    
        setLoading(false);
    };
    

    登录状态 submitPassword() 提交用户密码。

处理注册错误

登录过程中,并非所有操作都会成功。 例如,用户可能尝试使用不存在的用户名登录,或者提交无效的电子邮件一次性密码或不符合最低要求的密码。 请确保在以下情况下正确处理错误:

  • signIn() 方法中启动登录流。

  • 在方法中 submitCode() 提交一次性密码。

  • submitPassword() 方法中提交密码。 如果你选择的注册流是通过电子邮件和密码处理此错误。

方法产生的 signIn() 错误之一是 result.error?.isRedirectRequired()。 当本机身份验证不足以完成身份验证流时,会出现这种情况。 例如,如果授权服务器需要客户端无法提供的功能。 详细了解 本机身份验证 Web 回退 以及如何在 React 应用中 支持 Web 回退

运行并测试应用

使用 “运行”中的步骤并测试应用 以运行应用,然后测试登录流。

使用别名或用户名启用登录

你可以允许使用电子邮件地址和密码登录的用户也使用用户名和密码登录。 用户名也称为备用登录标识符,可以是客户 ID、帐户号或你选择用作用户名的另一个标识符。

可以通过 Microsoft Entra 管理中心手动将用户名分配给用户帐户,或者通过 Microsoft 图形 API 在应用中自动分配用户名。

使用《别名或用户名登录指南》文章中的步骤,让您的用户可以在应用程序中使用用户名登录:

  1. 在登录中启用用户名
  2. 在管理中心使用用户名创建用户,或通过添加用户名更新现有用户。 或者,还可以 使用 Microsoft 图形 API 在应用中自动创建和更新用户