教學課程:使用原生驗證 JavaScript SDK 登入 React 單頁應用程式

適用於: 帶有白色核取記號符號的綠色圓圈,表示下列內容適用於外部租用戶。 外部租用戶 (深入瞭解)

在本教學課程中,您將瞭解如何使用原生驗證 JavaScript SDK 將使用者登入 React 單頁應用程式 (SPA)。

在本教學課程中,您會:

  • 更新 React 應用程式以登入使用者。
  • 測試登入流程。

先決條件

建立UI元件

在這個章節,您會建立表單,用以收集使用者的登入資訊:

  1. 建立名為 src/app/sign-in 的資料夾。

  2. 建立 登入/components/InitialForm.tsx 檔案,然後從 sign-in/components/InitialForm.tsx 貼上程序代碼。 此元件會顯示一個表單,用於收集使用者的使用者名稱(電子郵件)。

  3. 如果您選擇的驗證方法是電子郵件和一次性密碼,請建立 登入/components/CodeForm.tsx 檔案,然後貼上 登入/components/CodeForm.tsx 的程序代碼。 如果系統管理員在 Microsoft Entra 系統管理中心將電子郵件一次性密碼設定為登入流程,則此元件會顯示表單體,以從使用者收集單次密碼。

  4. 如果您選擇的驗證方法是電子郵件和密碼,請建立 登入/components/PasswordForm.tsx 檔案,然後從 登入/components/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()會啟動登入流程。

    備註

    當租戶的使用者流程啟用 username 內建使用者屬性時,該參數會接受使用者的電子郵件地址或使用者名稱(別名)。 使用者可以輸入任一值來登入。 要啟用該屬性,請參見 登入識別碼政策中的啟用使用者名稱。

  • 如果您選擇的驗證流程是電子郵件和單次密碼,請使用下列代碼段提交一次性密碼。 請參閱 登入/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 後援 。

執行及測試您的應用程式

使用 執行和測試應用程式 中的步驟來執行您的應用程式,然後測試登入流程。