Esercitazione: Iscrivere gli utenti a un'app a pagina singola React usando JavaScript SDK per l'autenticazione nativa

Si applica a: cerchio verde con un segno di spunta bianco che indica che il contenuto seguente si applica ai tenant esterni. Tenant esterni (altre informazioni)

Questa esercitazione descrive come creare un'app a pagina singola React che consente agli utenti di iscriversi usando JavaScript SDK per l'autenticazione nativa.

In questa esercitazione, farai:

  • Creare un progetto react Next.js.
  • Aggiungi MSAL JS SDK a esso.
  • Aggiungere componenti dell'interfaccia utente dell'app.
  • Configura il progetto per registrare gli utenti.

Prerequisiti

Creare un progetto React e installare le dipendenze

In un percorso preferito nel computer eseguire i comandi seguenti per creare un nuovo progetto React con il nome reactspa, passare alla cartella del progetto e quindi installare i pacchetti:

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

Dopo aver eseguito correttamente i comandi, è necessario avere un'app con la struttura seguente:

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

Aggiungere JavaScript SDK al progetto

Per usare JavaScript SDK per l'autenticazione nativa nell'app, usare il terminale per installarlo usando il comando seguente:

npm install @azure/msal-browser

Le funzionalità di autenticazione nativa fanno parte della azure-msal-browser libreria. Per usare le funzionalità di autenticazione nativa, eseguire l'importazione da @azure/msal-browser/custom-auth. Per esempio:

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

Aggiungi la configurazione del client

In questa sezione viene definita una configurazione per l'applicazione client pubblica di autenticazione nativa per consentire l'interazione con l'interfaccia dell'SDK. A tale scopo, creare un file denominato src/config/auth-config.ts, quindi aggiungere il codice seguente:

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

Nel codice, trova il segnaposto:

  • Enter_the_Application_Id_Here sostituirlo quindi con l'ID applicazione (client) dell'app registrata in precedenza.

  • Enter_the_Tenant_Subdomain_Here sostituirlo quindi con il sottodominio tenant nell'interfaccia di amministrazione di Microsoft Entra. Ad esempio, se il dominio primario del tenant è contoso.onmicrosoft.com, usare contoso. Se non hai il nome del tuo locatario, scopri come leggere i dettagli del locatario.

Creare componenti dell'interfaccia utente

Questa app raccoglie i dettagli dell'utente, ad esempio il nome, il nome utente (indirizzo di posta elettronica), la password e un passcode monouso dell'utente. L'app deve quindi avere un modulo che raccoglie queste informazioni.

  1. Creare una cartella denominata src/app/sign-up nella cartella src .

  2. Creare il file sign-up/components/InitialForm.tsx , quindi incollare il codice da sign-up/components/InitialForm.tsx. Questo componente visualizza un modulo che raccoglie gli attributi di iscrizione utente.

  3. Creare un file sign-up/components/CodeForm.tsx , quindi incollare il codice da sign-up/components/CodeForm.tsx. Questo componente visualizza un modulo che raccoglie un passcode monouso inviato all'utente. È necessario questo modulo per la posta elettronica con password o posta elettronica con metodo di autenticazione con passcode monouso.

  4. Se la scelta del metodo di autenticazione è un messaggio di posta elettronica con password, creare un file di iscrizione/components/PasswordForm.tsx , quindi incollare il codice da sign-up/components/PasswordForm.tsx. Questo componente visualizza un modulo di input della password.

Gestione dell'interazione del modulo

In questa sezione viene aggiunto codice che gestisce le interazioni con i moduli di iscrizione, ad esempio l'invio dei dettagli di iscrizione utente o un passcode monouso o una password.

Crea sign-up/page.tsx per gestire la logica di un flusso di registrazione. In questo file:

  • Importare i componenti necessari e visualizzare il formato corretto in base allo stato. Consulta un esempio completo in 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>
        );
    }
    

    Questo codice crea anche un'istanza dell'app client pubblico di autenticazione nativa utilizzando la configurazione del client:

    const appInstance = await CustomAuthPublicClientApplication.create(customAuthConfig);
    setAuthClient(appInstance);
    
  • Per gestire l'invio iniziale del modulo, usare il frammento di codice seguente. Per informazioni su dove inserire il frammento di codice, vedere un esempio completo all'indirizzo sign-up/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);
    };
    

    Il metodo signUp() di istanza dell'SDK avvia il flusso di iscrizione.

  • Per gestire l'invio di passcode monouso, usare il frammento di codice seguente. Per informazioni su dove inserire il frammento di codice, vedere un esempio completo all'indirizzo sign-up/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);
        }
    };
    
  • Per gestire l'invio di password, usare il frammento di codice seguente. Gestisci l'invio di password se la tua scelta del metodo di autenticazione è la posta elettronica con password. Per informazioni su dove inserire il frammento di codice, vedere un esempio completo all'indirizzo sign-up/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);
        };
    
  • Usare signUpState instanceof SignUpCompletedState per indicare che l'utente è stato registrato e che il flusso è completo. Vedere un esempio completo all'indirizzo sign-up/page.tsx:

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

Chiedere un nome utente (alias) durante l'iscrizione

È possibile consentire agli utenti di iscriversi con un nome utente (alias) oltre alla posta elettronica. Il nome utente (alias) è un identificatore di accesso alternativo, ad esempio un ID cliente, un numero di account o un altro valore scelto.

Durante l'iscrizione, il nome utente (indirizzo di posta elettronica) è sempre necessario come identificatore primario e il nome utente (alias) non lo sostituisce. Per impostazione predefinita, il nome utente (alias) è facoltativo, anche se un amministratore può configurarlo in base alle esigenze. L'app raccoglie sempre il nome utente (indirizzo di posta elettronica) e raccoglie l'alias come attributo insieme al messaggio di posta elettronica. All'accesso, l'utente può quindi accedere con il nome utente (indirizzo di posta elettronica) o il nome utente (alias). Per informazioni su come l'attributo Username è configurato come facoltativo o obbligatorio, vedere Configurare i tipi di input utente e il layout di pagina.

Per raccogliere un nome utente (alias) durante l'iscrizione:

  1. Assicurarsi che l'attributo utente predefinito Username sia abilitato nel flusso utente di iscrizione. Per la procedura, vedere Abilitare il nome utente nei criteri di identificatore di accesso.

  2. Aggiungi uno stato flatUsername alla pagina di registrazione, quindi includi l'attributo flatusername nel UserAccountAttributes che passi a signUp():

    const [flatUsername, setFlatUsername] = useState("");
    
    const attributes: UserAccountAttributes = {
        displayName: `${firstName} ${lastName}`,
        //...
        flatusername: flatUsername,
    };
    
  3. Aggiungere un campo alias a InitialForm.tsx per acquisire il valore del nome utente (alias):

    <input
        type="text"
        placeholder="Username (alias)"
        value={flatUsername}
        onChange={(e) => setFlatUsername(e.target.value)}
        style={styles.input}
    />
    
  4. Gestire gli errori correlati al nome utente (alias):

    • result.error?.isUserAlreadyExists() copre un messaggio di posta elettronica duplicato o un nome utente duplicato (alias). Aggiornare il messaggio di conseguenza, ad esempio Un account con questo indirizzo di posta elettronica o nome utente esiste già.
    • Un nome utente (alias) non valido viene visualizzato tramite result.error?.isAttributesValidationFailed() anziché tramite result.error?.isInvalidUsername(). Creare un ramo in questo metodo per visualizzare un messaggio specifico del nome utente.

Gestire gli errori di iscrizione

Durante l'iscrizione, non tutte le azioni hanno esito positivo. Ad esempio, l'utente potrebbe tentare di iscriversi con un indirizzo di posta elettronica già usato o inviare un passcode monouso di posta elettronica non valido. Assicurarsi di gestire correttamente gli errori quando:

  • Avvia la procedura di registrazione nel metodo signUp().

  • Invia il codice di verifica monouso nel metodo submitCode().

  • Invia la password nel metodo submitPassword(). Gestisci questo errore se scegli un flusso di registrazione con email e password.

Uno degli errori che possono derivare dal signUp() metodo è result.error?.isRedirectRequired(). Questo scenario si verifica quando l'autenticazione nativa non è sufficiente per completare il flusso di autenticazione. Ad esempio, se il server di autorizzazione richiede funzionalità che il client non può fornire. Altre informazioni sul fallback Web di autenticazione nativa e su come supportare il fallback Web nell'app React.

Facoltativo: far accedere automaticamente gli utenti dopo la registrazione

Dopo l'iscrizione di un utente, è possibile accedere direttamente all'app senza avviare un nuovo flusso di accesso. A tale scopo, usare il frammento di codice seguente. Vedere un esempio completo all'indirizzo 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);
    }
}

Eseguire e testare l'app

  1. Aprire una finestra del terminale e passare alla cartella radice dell'app:

    cd reactspa
    
  2. Per avviare il server proxy CORS, eseguire il comando seguente nel terminale:

    npm run cors
    
  3. Per avviare l'app React, aprire un'altra finestra del terminale, quindi eseguire il comando seguente:

    cd reactspa
    npm start
    
  4. Aprire un Web browser e passare a http://localhost:3000/sign-up. Viene visualizzato un modulo di iscrizione.

  5. Per iscriversi a un account, immettere i dettagli, selezionare il pulsante Continua e quindi seguire le istruzioni.

È quindi possibile aggiornare l'app React per accedere a un utente o reimpostare la password dell'utente.

Configurare poweredByHeader su false in next.config.js

Per impostazione predefinita, l'intestazione x-powered-by è inclusa nelle risposte HTTP per indicare che l'applicazione è basata su Next.js. Tuttavia, per motivi di sicurezza o personalizzazione, è possibile rimuovere o modificare questa intestazione:

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

Passo successivo