Samouczek: rejestrowanie użytkowników w aplikacji jednostronicowej React za pomocą natywnego zestawu SDK uwierzytelniania dla języka JavaScript

Dotyczy: Zielone kółko z białym znacznikiem wyboru oznacza, że poniższa treść dotyczy dzierżaw zewnętrznych. Dzierżawy zewnętrzne (dowiedz się więcej)

Z tego samouczka dowiesz się, jak utworzyć jednostronicową aplikację platformy React, która zarejestruje użytkowników przy użyciu zestawu SDK języka JavaScript natywnego uwierzytelniania.

W tym samouczku nauczysz się następujących rzeczy:

  • Utwórz projekt react Next.js.
  • Dodaj do niego zestaw MSAL JS SDK.
  • Dodaj składniki interfejsu użytkownika aplikacji.
  • Skonfiguruj projekt, aby zarejestrować użytkowników.

Wymagania wstępne

Tworzenie projektu React i instalowanie zależności

W wybranej lokalizacji na komputerze uruchom następujące polecenia, aby utworzyć nowy projekt React o nazwie reactspa, przejdź do folderu projektu, a następnie zainstaluj pakiety:

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

Po pomyślnym uruchomieniu poleceń należy mieć aplikację z następującą strukturą:

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

Dodawanie zestawu JAVAScript SDK do projektu

Aby użyć w aplikacji natywnego pakietu SDK uwierzytelniania dla języka JavaScript, użyj terminala i zainstaluj go za pomocą następującego polecenia:

npm install @azure/msal-browser

Możliwości uwierzytelniania natywnego są częścią azure-msal-browser biblioteki. Aby użyć natywnych funkcji uwierzytelniania, należy zaimportować z @azure/msal-browser/custom-auth. Przykład:

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

Dodawanie konfiguracji klienta

W tej sekcji zdefiniujesz konfigurację dla natywnej publicznej aplikacji klienckiej uwierzytelniania, aby umożliwić jej interakcję z interfejsem zestawu SDK. W tym celu utwórz plik o nazwie src/config/auth-config.ts, a następnie dodaj następujący kod:

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;
        }
      },
    },
  },
};

W kodzie znajdź symbol zastępczy:

  • Enter_the_Application_Id_Here następnie zastąp go identyfikatorem aplikacji (klienta) aplikacji zarejestrowanej wcześniej.

  • Enter_the_Tenant_Subdomain_Here następnie zastąp ją poddomeną dzierżawy w centrum administracyjnym firmy Microsoft Entra. Na przykład, jeśli podstawowa domena najemcy to contoso.onmicrosoft.com, użyj contoso. Jeśli nie masz nazwy najemcy, dowiedz się, jak sprawdzić szczegóły najemcy.

Tworzenie składników interfejsu użytkownika

Ta aplikacja zbiera szczegóły użytkownika, takie jak imię i nazwisko, nazwa użytkownika (e-mail), hasło i jednorazowy kod dostępu od użytkownika. Dlatego aplikacja musi mieć formularz zbierający te informacje.

  1. Utwórz folder o nazwie src/app/sign-up w folderze src .

  2. Utwórz plik sign-up/components/InitialForm.tsx , a następnie wklej kod z pliku sign-up/components/InitialForm.tsx. Ten składnik wyświetla formularz zbierający atrybuty rejestracji użytkownika.

  3. Utwórz plik sign-up/components/CodeForm.tsx , a następnie wklej kod z pliku sign-up/components/CodeForm.tsx. Ten składnik wyświetla formularz, który zbiera jednorazowy kod dostępu wysyłany do użytkownika. Ten formularz jest wymagany dla poczty e-mail z hasłem lub pocztą e-mail z jednorazową metodą uwierzytelniania kodu dostępu.

  4. Jeśli wybrana metoda uwierzytelniania to wiadomość e-mail z hasłem, utwórz plik sign-up/components/PasswordForm.tsx , a następnie wklej kod z pliku sign-up/components/PasswordForm.tsx. Ten składnik wyświetla formularz wejściowy hasła.

Obsługa interakcji z formularzem

W tej sekcji dodasz kod, który obsługuje interakcje formularza rejestracji, takie jak przesyłanie szczegółów rejestracji użytkownika lub jednorazowego kodu dostępu lub hasła.

Utwórz plik sign-up/page.tsx , aby obsłużyć logikę przepływu rejestracji. Zawartość tego pliku:

  • Zaimportuj niezbędne składniki i wyświetl odpowiedni formularz na podstawie stanu. Zobacz pełny przykład w pliku sign-up/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>
        );
    }
    

    Ten kod tworzy również instancję natywnej publicznej aplikacji klienckiej do uwierzytelniania z użyciem konfiguracji klienta:

    const appInstance = await CustomAuthPublicClientApplication.create(customAuthConfig);
    setAuthClient(appInstance);
    
  • Aby obsłużyć przesyłanie początkowego formularza, użyj następującego fragmentu kodu. Zobacz pełny przykład na stronie sign-up/page.tsx , aby dowiedzieć się, gdzie umieścić fragment kodu:

    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);
    };
    

    Metoda instancji SDK, signUp(), uruchamia proces rejestracji.

  • Aby obsłużyć jednorazowe przesyłanie kodu dostępu, użyj następującego fragmentu kodu. Zobacz pełny przykład na stronie sign-up/page.tsx , aby dowiedzieć się, gdzie umieścić fragment kodu:

    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);
        }
    };
    
  • Aby obsłużyć przesyłanie haseł, użyj następującego fragmentu kodu. Przesyłanie hasła jest obsługiwane, jeśli wybrana metoda uwierzytelniania to wiadomość e-mail z hasłem. Zobacz pełny przykład na stronie sign-up/page.tsx , aby dowiedzieć się, gdzie umieścić fragment kodu:

        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);
        };
    
  • Użyj signUpState instanceof SignUpCompletedState, aby oznaczyć, że użytkownik został zarejestrowany i proces został zakończony. Zobacz pełny przykład na stronie sign-up/page.tsx:

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

Zbieranie nazwy użytkownika (aliasu) podczas rejestracji

Możesz zezwolić użytkownikom na rejestrację przy użyciu nazwy użytkownika (aliasu) oprócz poczty e-mail. Nazwa użytkownika (alias) to alternatywny identyfikator logowania, taki jak identyfikator klienta, numer konta lub inna wybrana wartość.

Podczas rejestracji nazwa użytkownika (adres e-mail) jest zawsze wymagana jako identyfikator podstawowy, a nazwa użytkownika (alias) nie zastępuje go. Domyślnie nazwa użytkownika (alias) jest opcjonalna, chociaż administrator może skonfigurować ją zgodnie z potrzebami. Aplikacja zawsze zbiera nazwę użytkownika (adres e-mail) i zbiera alias jako atrybut obok wiadomości e-mail. Podczas logowania użytkownik może następnie zalogować się przy użyciu nazwy użytkownika (adresu e-mail) lub nazwy użytkownika (aliasu). Aby dowiedzieć się, jak atrybut nazwy użytkownika jest skonfigurowany jako opcjonalny lub wymagany, zobacz Konfigurowanie typów danych wejściowych użytkownika i układu strony.

Aby zebrać nazwę użytkownika (alias) podczas rejestracji:

  1. Upewnij się, że wbudowany atrybut użytkownika username jest włączony w przepływie użytkownika rejestracji. Aby uzyskać instrukcje, zobacz Włączanie nazwy użytkownika w zasadach identyfikatora logowania.

  2. Dodaj stan flatUsername do strony rejestracji, a następnie uwzględnij atrybut flatusername w obiekcie UserAccountAttributes, który przekazujesz do signUp():

    const [flatUsername, setFlatUsername] = useState("");
    
    const attributes: UserAccountAttributes = {
        displayName: `${firstName} ${lastName}`,
        //...
        flatusername: flatUsername,
    };
    
  3. Dodaj dane wejściowe aliasu do pliku InitialForm.tsx , aby zebrać wartość nazwy użytkownika (aliasu):

    <input
        type="text"
        placeholder="Username (alias)"
        value={flatUsername}
        onChange={(e) => setFlatUsername(e.target.value)}
        style={styles.input}
    />
    
  4. Obsługa błędów związanych z nazwą użytkownika (alias):

    • result.error?.isUserAlreadyExists() obejmuje zduplikowaną wiadomość e-mail lub zduplikowaną nazwę użytkownika (alias). Zaktualizuj odpowiednio wiadomość, na przykład Konto z tą nazwą e-mail lub nazwą użytkownika już istnieje.
    • Nieprawidłowa nazwa użytkownika (alias) jest wyświetlana za pośrednictwem result.error?.isAttributesValidationFailed(), a nie result.error?.isInvalidUsername(). Rozgałęź tę metodę, aby wyświetlić komunikat specyficzny dla użytkownika.

Obsługa błędów rejestracji

Podczas rejestracji nie wszystkie akcje kończą się powodzeniem. Na przykład użytkownik może próbować zarejestrować się przy użyciu już używanego adresu e-mail lub przesłać nieprawidłowy kod dostępu jednorazowego wiadomości e-mail. Upewnij się, że prawidłowo obsłużysz błędy, gdy:

  • Uruchom przepływ rejestracji w metodzie signUp() .

  • Prześlij jednorazowy kod dostępu w metodzie submitCode() .

  • Prześlij hasło w metodzie submitPassword() . Ten błąd obsługujesz, jeśli wybrany sposób rejestracji wykorzystuje adres e-mail i hasło.

Jednym z błędów, które mogą wynikać z signUp() metody, jest result.error?.isRedirectRequired(). Ten scenariusz występuje, gdy uwierzytelnianie natywne nie jest wystarczające do ukończenia przepływu uwierzytelniania. Jeśli na przykład serwer autoryzacji wymaga możliwości, których klient nie może podać. Dowiedz się więcej o internetowym mechanizmie awaryjnym uwierzytelniania natywnego i o tym, jak obsługiwać internetowy mechanizm awaryjny w aplikacji React.

Opcjonalnie: automatyczne logowanie użytkowników po zarejestrowaniu

Po pomyślnym zarejestrowaniu użytkownika możesz bezpośrednio zalogować się do aplikacji bez inicjowania nowego przepływu logowania. W tym celu użyj następującego fragmentu kodu. Zobacz pełny przykład na stronie sign-up/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);
    }
}

Uruchamianie i testowanie aplikacji

  1. Otwórz okno terminalu i przejdź do folderu głównego aplikacji:

    cd reactspa
    
  2. Aby uruchomić serwer proxy CORS, uruchom następujące polecenie w terminalu:

    npm run cors
    
  3. Aby uruchomić aplikację React, otwórz kolejne okno terminalu, a następnie uruchom następujące polecenie:

    cd reactspa
    npm start
    
  4. Otwórz przeglądarkę internetową i przejdź pod adres http://localhost:3000/sign-up. Zostanie wyświetlony formularz rejestracji.

  5. Aby utworzyć konto, wprowadź szczegóły, wybierz przycisk Kontynuuj , a następnie postępuj zgodnie z monitami.

Następnie możesz zaktualizować aplikację React, aby zalogować użytkownika lub zresetować hasło użytkownika.

Konfigurowanie elementu poweredByHeader na wartość false w next.config.js

Domyślnie nagłówek x-powered-by jest dołączany do odpowiedzi HTTP, aby wskazać, że aplikacja jest oparta na Next.js. Jednak ze względów bezpieczeństwa lub dostosowywania możesz usunąć lub zmodyfikować ten nagłówek:

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

Następny krok