共用方式為


教學課程:使用原生驗證 JavaScript SDK 將用戶註冊到 React 單頁應用程式 (預覽)

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

在本教學課程中,您將瞭解如何建置 React 單頁應用程式,以使用原生驗證的 JavaScript SDK 來註冊使用者。

在本教學課程中,您會:

  • 建立 React Next.js 專案。
  • 將 MSAL JS SDK 新增至其中。
  • 新增應用程式的UI元件。
  • 設定專案以註冊使用者。

先決條件

建立 React 專案並安裝相依性

在計算機中選擇的位置中,執行下列命令以建立名為 reactspa的新 React 專案,流覽至專案資料夾,然後安裝套件:

npx create-next-app@latest
cd reactspa
npm install

成功執行命令之後,您應該會有具有下列結構的應用程式:

spasample/
└──node_modules/
   └──...
└──public/
   └──...
└──src/
   └──app/
      └──favicon.ico
      └──globals.css
      └──page.tsx
      └──layout.tsx
└──postcss.config.mjs
└──package-lock.json
└──package.json
└──tsconfig.json
└──README.md
└──next-env.d.ts
└──next.config.ts

將 JavaScript SDK 新增至您的專案

若要在應用程式中使用原生驗證 JavaScript SDK,請使用您的終端機,使用下列命令進行安裝:

npm install @azure/msal-browser

原生驗證功能是 azure-msal-browser 程式庫的一部分。 若要使用原生驗證功能,您可以從 匯入 @azure/msal-browser/custom-auth。 例如:

  import CustomAuthPublicClientApplication from "@azure/msal-browser/custom-auth";

新增客戶端設定

在本節中,您會定義原生驗證公用用戶端應用程式的組態,使其能夠與 SDK 的介面互動。 若要這樣做,請建立名為 src/config/auth-config.ts 的檔案,然後新增下列程式代碼:

export const customAuthConfig: CustomAuthConfiguration = {
  customAuth: {
    challengeTypes: ["password", "oob", "redirect"],
    authApiProxyUrl: "http://localhost:3001/api",
  },
  auth: {
    clientId: "Enter_the_Application_Id_Here",
    authority: "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com",
    redirectUri: "/",
    postLogoutRedirectUri: "/",
    navigateToLoginRequestUrl: false,
  },
  cache: {
    cacheLocation: "sessionStorage",
  },
  system: {
    loggerOptions: {
      loggerCallback: (
        level: LogLevel,
        message: string,
        containsPii: boolean
      ) => {
        if (containsPii) {
          return;
        }
        switch (level) {
          case LogLevel.Error:
            console.error(message);
            return;
          case LogLevel.Info:
            console.info(message);
            return;
          case LogLevel.Verbose:
            console.debug(message);
            return;
          case LogLevel.Warning:
            console.warn(message);
            return;
        }
      },
    },
  },
};

在程式代碼中,尋找佔位符:

  • Enter_the_Application_Id_Here 然後將它取代為您稍早註冊之應用程式的應用程式(用戶端)標識碼。

  • Enter_the_Tenant_Subdomain_Here,然後將它替換為您 Microsoft Entra 系統管理中心的租用戶子域。 例如,如果您的租戶主要網域為 contoso.onmicrosoft.com,則請使用 contoso。 如果您沒有租用戶名稱,請了解如何讀取租用戶詳細資料

建立UI元件

此應用程式會收集使用者詳細數據,例如指定的名稱、使用者名稱(電子郵件)、密碼,以及使用者的一次性密碼。 因此,應用程式必須有一個窗體來收集這項資訊。

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

  2. 建立 註冊/元件/InitialForm.tsx 檔案,然後從 註冊/元件/InitialForm.tsx 貼上程序代碼。 此元件會顯示收集用戶註冊屬性的表單。

  3. 建立 註冊/元件/CodeForm.tsx 檔案,然後從 註冊/元件/CodeForm.tsx 貼上程序代碼。 此元件會顯示一個表單,用以收集發送給使用者的一次性密碼。 您需要此表單來使用電子郵件搭配密碼或電子郵件搭配一次性驗證碼的身份驗證方式。

  4. 如果您選擇的驗證方法是 具有密碼的電子郵件,請建立 註冊/元件/PasswordForm.tsx 檔案,然後從 註冊/元件/PasswordForm.tsx 貼上程序代碼。 此元件會顯示密碼輸入表單。

處理表單互動

在本節中,您會新增程式碼來處理註冊表單互動,例如提交用戶註冊詳細資料、一次性密碼或密碼。

建立 註冊/page.tsx 來處理註冊流程的邏輯。 在這個檔案中:

  • 匯入必要的元件,並根據狀態顯示適當的表單。 請參閱 註冊/page.tsx 的完整範例:

        import { useEffect, useState } from "react";
        import { customAuthConfig } from "../../config/auth-config";
        import { styles } from "./styles/styles";
        import { InitialFormWithPassword } from "./components/InitialFormWithPassword";
    
        import {
        CustomAuthPublicClientApplication,
        ICustomAuthPublicClientApplication,
        SignUpCodeRequiredState,
        // Uncomment if your choice of authentication method is email with password
        // SignUpPasswordRequiredState,
        SignUpCompletedState,
        AuthFlowStateBase,
      } from "@azure/msal-browser/custom-auth";
    
        import { SignUpResultPage } from "./components/SignUpResult";
        import { CodeForm } from "./components/CodeForm";
        import { PasswordForm } from "./components/PasswordForm";    
    export default function SignUpPassword() {
        const [authClient, setAuthClient] = useState<ICustomAuthPublicClientApplication | null>(null);
        const [firstName, setFirstName] = useState("");
        const [lastName, setLastName] = useState("");
        const [jobTitle, setJobTitle] = useState("");
        const [city, setCity] = useState("");
        const [country, setCountry] = useState("");
        const [email, setEmail] = useState("");
        //Uncomment if your choice of authentication method is email with password
        //const [password, setPassword] = useState("");
        const [code, setCode] = useState("");
        const [error, setError] = useState("");
        const [loading, setLoading] = useState(false);
        const [signUpState, setSignUpState] = useState<AuthFlowStateBase | null>(null);
        const [loadingAccountStatus, setLoadingAccountStatus] = useState(true);
        const [isSignedIn, setSignInState] = 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()) {
                    setSignInState(true);
                }
                setLoadingAccountStatus(false);
            };
            checkAccount();
        }, [authClient]);
    
        const renderForm = () => {
            if (loadingAccountStatus) {
                return;
            }
            if (isSignedIn) {
                return (
                    <div style={styles.signed_in_msg}>Please sign out before processing the sign up.</div>
                );
            }
            if (signUpState instanceof SignUpCodeRequiredState) {
                return (
                    <CodeForm
                        onSubmit={handleCodeSubmit}
                        code={code}
                        setCode={setCode}
                        loading={loading}
                    />
                );
            } 
            //Uncomment the following block of code if your choice of authentication method is email with password 
            /*
            else if(signUpState instanceof SignUpPasswordRequiredState) {
                return <PasswordForm
                    onSubmit={handlePasswordSubmit}
                    password={password}
                    setPassword={setPassword}
                    loading={loading}
                />;
            }
            */
            else if (signUpState instanceof SignUpCompletedState) {
                return <SignUpResultPage />;
            } else {
                return (
                    <InitialForm
                        onSubmit={handleInitialSubmit}
                        firstName={firstName}
                        setFirstName={setFirstName}
                        lastName={lastName}
                        setLastName={setLastName}
                        jobTitle={jobTitle}
                        setJobTitle={setJobTitle}
                        city={city}
                        setCity={setCity}
                        country={country}
                        setCountry={setCountry}
                        email={email}
                        setEmail={setEmail}
                        loading={loading}
                    />
                );
            }
        }
        return (
            <div style={styles.container}>
                <h2 style={styles.h2}>Sign Up</h2>
                {renderForm()}
                {error && <div style={styles.error}>{error}</div>}
            </div>
        );
    }
    

    此程式代碼也會使用 用戶端群組態建立原生驗證公用用戶端應用程式的實例:

    const appInstance = await CustomAuthPublicClientApplication.create(customAuthConfig);
    setAuthClient(appInstance);
    
  • 若要處理初始表單提交,請使用下列代碼段。 請參閱 註冊/page.tsx 的完整範例,以瞭解放置代碼段的位置:

    const handleInitialSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        setError("");
        setLoading(true);
    
        if (!authClient) return;
    
        const attributes: UserAccountAttributes = {
            displayName: `${firstName} ${lastName}`,
            givenName: firstName,
            surname: lastName,
            jobTitle: jobTitle,
            city: city,
            country: country,
        };
    
        const result = await authClient.signUp({
            username: email,
            attributes
        });
        const state = result.state;
    
        if (result.isFailed()) {
            if (result.error?.isUserAlreadyExists()) {
                setError("An account with this email already exists");
            } else if (result.error?.isInvalidUsername()) {
                setError("Invalid uername");
            } else if (result.error?.isInvalidPassword()) {
                setError("Invalid password");
            } else if (result.error?.isAttributesValidationFailed()) {
                setError("Invalid attributes");
            } else if (result.error?.isMissingRequiredAttributes()) {
                setError("Missing required attributes");
            } else {
                setError(result.error?.errorData.errorDescription || "An error occurred while signing up");
            }
        } else {
            setSignUpState(state);
        }
        setLoading(false);
    };
    

    SDK 的實例方法會 signUp() 啟動註冊流程。

  • 若要處理單次密碼提交,請使用下列代碼段。 請參閱 註冊/page.tsx 的完整範例,以瞭解放置代碼段的位置:

    const handleCodeSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        setError("");
        setLoading(true);
    
        try {
            if (signUpState instanceof SignUpCodeRequiredState) {
                const result = await signUpState.submitCode(code);
                if (result.error) {
                    if (result.error.isInvalidCode()) {
                        setError("Invalid verification code");
                    } else {
                        setError("An error occurred while verifying the code");
                    }
                    return;
                }
                if (result.state instanceof SignUpCompletedState) {
                    setSignUpState(result.state);
                }
            }
        } catch (err) {
            setError("An unexpected error occurred");
            console.error(err);
        } finally {
            setLoading(false);
        }
    };
    
  • 若要處理密碼提交,請使用下列代碼段。 如果您選擇的驗證方法是 具有密碼的電子郵件,您就會處理密碼提交。 請參閱 註冊/page.tsx 的完整範例,以瞭解放置代碼段的位置:

        const handlePasswordSubmit = async (e: React.FormEvent) => {
            e.preventDefault();
            setError("");
            setLoading(true);
    
            if (signUpState instanceof SignUpPasswordRequiredState) {
                const result = await signUpState.submitPassword(password);
                const state = result.state;
    
                if (result.isFailed()) {
                    if (result.error?.isInvalidPassword()) {
                        setError("Invalid password");
                    } else {
                        setError(result.error?.errorData.errorDescription || "An error occurred while submitting the password");
                    }
                } else {
                    setSignUpState(state);
                }
            }
    
            setLoading(false);
        };
    
  • signUpState instanceof SignUpCompletedState使用 來表示用戶已註冊且流程已完成。 請參閱 註冊/page.tsx 的完整範例:

    if (signUpState instanceof SignUpCompletedState) {
        return <SignUpResultPage/>;
    }
    

處理註冊錯誤

註冊期間,並非所有動作都成功。 例如,使用者可能會嘗試使用已使用的電子郵件地址註冊,或提交無效的電子郵件一次性密碼。 請確保在處理錯誤時正確應對問題:

  • signUp() 方法中啟動註冊流程。

  • 提交一次性密碼於 submitCode() 方法中。

  • 請在 submitPassword() 方法中提交密碼。 如果您選擇的註冊流程是透過電子郵件和密碼,您就會處理此錯誤。

使用 signUp() 方法可能產生的錯誤之一是 result.error?.isRedirectRequired()。 當原生驗證不足以完成驗證流程時,就會發生此案例。 例如,如果授權伺服器需要客戶端無法提供的功能。 深入瞭解 原生驗證 Web 後援 ,以及如何在 React 應用程式中 支援 Web 後援

選擇性:註冊之後自動登入使用者

使用者成功註冊之後,您可以直接將他們登入應用程式,而不需要起始新的登入流程。 若要這樣做,請使用下列代碼段。 請參閱 註冊/page.tsx 的完整範例:

if (signUpState instanceof SignUpCompletedState) {
    const result = await signUpState.signIn();
    const state = result.state;
    if (result.isFailed()) {
        setError(result.error?.errorData?.errorDescription || "An error occurred during auto sign-in");
    }
    
    if (result.isCompleted()) {
        setData(result.data);
        setSignUpState(state);
    }
}

執行及測試您的應用程式

  1. 開啟終端機視窗並瀏覽至您應用程式的根資料夾:

    cd reactspa
    
  2. 若要啟動 CORS Proxy 伺服器,請在終端機中執行下列命令:

    npm run cors
    
  3. 若要啟動 React 應用程式,請開啟另一個終端機視窗,然後執行下列命令:

    cd reactspa
    npm start
    
  4. 開啟 web 瀏覽器並巡覽至 http://localhost:3000/sign-up。 註冊表單出現。

  5. 若要註冊帳戶,請輸入您的詳細數據,選取 [ 繼續 ] 按鈕,然後遵循提示。

接下來,您可以更新 React 應用程式以登入使用者或重設使用者的密碼。

在 next.config.js 中將poweredByHeader設定為 false

根據預設, x-powered-by 標頭會包含在 HTTP 回應中,表示應用程式是由 Next.js提供電源。 不過,基於安全性或自定義原因,您可能會想要移除或修改此標頭:

const nextConfig: NextConfig = {
  poweredByHeader: false,
  /* other config options here */
};

後續步驟