Partager via


Tutoriel : Inscrire des utilisateurs dans une application à page unique React à l’aide de l’authentification native (préversion)

S’applique à : cercle vert avec un symbole de coche blanc qui indique que le contenu suivant s’applique aux locataires externes. Locataires externes (en savoir plus)

Dans ce tutoriel, vous allez apprendre à créer une application à page unique React qui inscrit des utilisateurs à l’aide de l’authentification native.

Dans ce tutoriel, vous allez :

  • Créez un projet React.
  • Ajoutez des composants d’interface utilisateur de l’application.
  • Configurez le projet pour inscrire l’utilisateur à l’aide du nom d’utilisateur (e-mail) et du mot de passe.

Conditions préalables

Créer un projet React et installer des dépendances

Dans un emplacement de choix dans votre ordinateur, exécutez les commandes suivantes pour créer un projet React avec le nom reactspa, accédez au dossier du projet, puis installez les packages :

npm config set legacy-peer-deps true
npx create-react-app reactspa --template typescript
cd reactspa
npm install ajv
npm install react-router-dom
npm install

Ajouter un fichier de configuration pour votre application

Créez un fichier appelé src/config.js, puis ajoutez le code suivant :

// App Id obatained from the Microsoft Entra portal 
export const CLIENT_ID = "Enter_the_Application_Id_Here";

// URL of the CORS proxy server
const BASE_API_URL = `http://localhost:3001/api`;

// Endpoints URLs for Native Auth APIs
export const ENV = {
    urlSignupStart: `${BASE_API_URL}/signup/v1.0/start`,
    urlSignupChallenge: `${BASE_API_URL}/signup/v1.0/challenge`,
    urlSignupContinue: `${BASE_API_URL}/signup/v1.0/continue`,
}
  • Recherchez la valeur Enter_the_Application_Id_Here et remplacez-la par l’ID d’application (clientId) de l’application que vous avez inscrite dans le Centre d’administration Microsoft Entra.

  • Le BASE_API_URL pointe vers un serveur proxy de Partage de ressources entre origines (CORS), que nous mettrons en place plus tard dans cette série de tutoriels. L’API d’authentification native ne prend pas en charge CORS. Nous avons donc configuré un serveur proxy CORS entre la spa React et l’API d’authentification native pour gérer les en-têtes CORS.

Configurer l’application React pour appeler l’API d’authentification native et gérer la réponse

Pour compléter un flux d’authentification, tel qu’un flux d’inscription, avec les API d’authentification natives, l’application effectue des appels et gère les réponses. Par exemple, l’application lance un flux d’inscription et attend une réponse, puis envoie des attributs utilisateur et attend à nouveau que l’utilisateur soit correctement inscrit.

Configurer l’appel client vers l’API d’authentification native

Dans cette section, vous allez définir comment effectuer des appels à l’authentification native et gérer les réponses :

  1. Créez un dossier appelé client dans le dossier src.

  2. Créez un fichier appelé scr/client/RequestClient.ts, puis ajoutez l’extrait de code suivant :

    import { ErrorResponseType } from "./ResponseTypes";
    
    export const postRequest = async (url: string, payloadExt: any) => {
    const body = new URLSearchParams(payloadExt as any);
    
    const response = await fetch(url, {
        method: "POST",
        headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        },
        body,
    });
    
    if (!response.ok) {
        try {
        const errorData: ErrorResponseType = await response.json();
        throw errorData;
        } catch (jsonError) {
        const errorData = {
            error: response.status,
            description: response.statusText,
            codes: [],
            timestamp: "",
            trace_id: "",
            correlation_id: "",
        };
        throw errorData;
        }
    }
    
    return await response.json();
    };
    

    Ce code définit la façon dont l’application effectue des appels à l’API d’authentification native et gère les réponses. Chaque fois que l’application doit lancer un flux d’authentification, elle utilise la fonction postRequest en spécifiant les données d’URL et de charge utile.

Définir les types d’appels effectués par l’application à l’API d’authentification native

Pendant le flux d’inscription, l’application effectue plusieurs appels à l’API d’authentification native.

Pour définir ces appels, créez un fichier appelé scr/client/RequestTypes.ts, puis ajoutez l’extrait de code suivant :

    //SignUp 
    export interface SignUpStartRequest {
        client_id: string;
        username: string;
        challenge_type: string;
        password?: string;
        attributes?: Object;
    }
    
    export interface SignUpChallengeRequest {
        client_id: string;
        continuation_token: string;
        challenge_type?: string;
    }
    
    export interface SignUpFormPassword {
        name: string;
        surname: string;
        username: string;
        password: string;
    }
    
    //OTP
    export interface ChallengeForm {
        continuation_token: string;
        oob?: string;
        password?: string;
    }

Définir le type de réponse reçu par l’API d’authentification native

Pour définir le type de réponses que l’application peut recevoir à partir de l’API d’authentification native pour l’opération d’inscription, créez un fichier appelé src/client/ResponseTypes.ts, puis ajoutez l’extrait de code suivant :

    export interface SuccessResponseType {
    continuation_token?: string;
    challenge_type?: string;
    }
    
    export interface ErrorResponseType {
        error: string;
        error_description: string;
        error_codes: number[];
        timestamp: string;
        trace_id: string;
        correlation_id: string;
    }
        
    export interface ChallengeResponse {
        binding_method: string;
        challenge_channel: string;
        challenge_target_label: string;
        challenge_type: string;
        code_length: number;
        continuation_token: string;
        interval: number;
    }

Traiter les demandes d’inscription

Dans cette section, vous ajoutez du code qui traite les demandes liées au processus d'inscription. Par exemple, ces demandes démarrent un flux d’inscription, sélectionnent une méthode d’authentification et envoient un code secret à usage unique.

Pour ce faire, créez un fichier appelé src/client/SignUpService.ts, puis ajoutez l’extrait de code suivant :

import { CLIENT_ID, ENV } from "../config";
import { postRequest } from "./RequestClient";
import { ChallengeForm, SignUpChallengeRequest, SignUpFormPassword, SignUpStartRequest } from "./RequestTypes";
import { ChallengeResponse } from "./ResponseTypes";

//handle start a sign-up flow
export const signupStart = async (payload: SignUpFormPassword) => {
const payloadExt: SignUpStartRequest = {
    attributes: JSON.stringify({
    given_name: payload.name,
    surname: payload.surname,
    }),
    username: payload.username,
    password: payload.password,
    client_id: CLIENT_ID,
    challenge_type: "password oob redirect",
};

return await postRequest(ENV.urlSignupStart, payloadExt);
};

//handle selecting an authentication method
export const signupChallenge = async (payload: ChallengeForm):Promise<ChallengeResponse> => {
    const payloadExt: SignUpChallengeRequest = {
        client_id: CLIENT_ID,
        challenge_type: "password oob redirect",
        continuation_token: payload.continuation_token,
    };

    return await postRequest(ENV.urlSignupChallenge, payloadExt);
};

//handle submit one-time passcode
export const signUpSubmitOTP = async (payload: ChallengeForm) => {
    const payloadExt = {
        client_id: CLIENT_ID,
        continuation_token: payload.continuation_token,
        oob: payload.oob,
        grant_type: "oob",
    };

    return await postRequest(ENV.urlSignupContinue, payloadExt);
};

La propriété challenge_type affiche les méthodes d’authentification que l’application cliente prend en charge. Cette application se connecte en utilisant un e-mail avec mot de passe. Par conséquent, la valeur du type de défi est redirection de mot de passe oob. En savoir plus sur les types de défis .

Créer des composants d’interface utilisateur

Cette application collecte les détails de l’utilisateur, tels que le nom donné, le nom d’utilisateur (e-mail) et le mot de passe et un code secret à usage unique de l’utilisateur. Par conséquent, l’application doit avoir un formulaire d’inscription et un formulaire de collecte de code secret à usage unique.

  1. Créez un dossier appelé /pages/SignUp dans le dossier src.

  2. Pour créer, afficher et envoyer le formulaire d’inscription, créez un fichier src/pages/SignUp/SignUp.tsx, puis ajoutez le code suivant :

        import React, { useState } from 'react';
        import { signupChallenge, signupStart } from '../../client/SignUpService';
        import { useNavigate } from 'react-router-dom';
        import { ErrorResponseType } from "../../client/ResponseTypes";
    
        export const SignUp: React.FC = () => {
            const [name, setName] = useState<string>('');
            const [surname, setSurname] = useState<string>('');
            const [email, setEmail] = useState<string>('');
            const [error, setError] = useState<string>('');
            const [isLoading, setIsloading] = useState<boolean>(false);
            const navigate = useNavigate();
            const validateEmail = (email: string): boolean => {
              const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
              return re.test(String(email).toLowerCase());
            };
    
            const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
              e.preventDefault();
              if (!name || !surname || !email) {
                setError('All fields are required');
                return;
              }
              if (!validateEmail(email)) {
                setError('Invalid email format');
                return;
              }
              setError('');
              try {
                setIsloading(true);
                const res1 = await signupStart({ name, surname, username: email, password });
                const res2 = await signupChallenge({ continuation_token: res1.continuation_token });
                navigate('/signup/challenge', { state: { ...res2} });
              } catch (err) {
                setError("An error occurred during sign up " + (err as ErrorResponseType).error_description);
              } finally {
                setIsloading(false);
              }
            };
    
            return (
              <div className="sign-up-form">
                <form onSubmit={handleSubmit}>
                  <h2>Sign Up</h2>
                  <div className="form-group">
                    <label>Name:</label>
                    <input
                      type="text"
                      value={name}
                      onChange={(e) => setName(e.target.value)}
                      required
                    />
                  </div>
                  <div className="form-group">
                    <label>Last Name:</label>
                    <input
                      type="text"
                      value={surname}
                      onChange={(e) => setSurname(e.target.value)}
                      required
                    />
                  </div>
                  <div className="form-group">
                    <label>Email:</label>
                    <input
                      type="email"
                      value={email}
                      onChange={(e) => setEmail(e.target.value)}
                      required
                    />
                  </div>
                  {error && <div className="error">{error}</div>}
                  {isLoading && <div className="warning">Sending request...</div>}
                  <button type="submit">Sign Up</button>
                </form>
              </div>
            );
          };
    
  3. Pour créer, afficher et soumettre le formulaire de code secret à usage unique, créez un fichier src/pages/signup/SignUpChallenge.tsx, puis ajoutez le code suivant :

    import React, { useState } from "react";
    import { useNavigate, useLocation } from "react-router-dom";
    import { signUpSubmitOTP } from "../../client/SignUpService";
    import { ErrorResponseType } from "../../client/ResponseTypes";
    
    export const SignUpChallenge: React.FC = () => {
      const { state } = useLocation();
      const navigate = useNavigate();
      const { challenge_target_label, challenge_type, continuation_token, code_length } = state;
    
      const [code, setCode] = useState<string>("");
      const [error, setError] = useState<string>("");
      const [isLoading, setIsloading] = useState<boolean>(false);
    
      const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        if (!code) {
          setError("All fields are required");
          return;
        }
    
        setError("");
        try {
          setIsloading(true);
          const res = await signUpSubmitOTP({ continuation_token, oob: code });
          navigate("/signup/completed");
        } catch (err) {
          setError("An error occurred during sign up " + (err as ErrorResponseType).error_description);
        } finally {
          setIsloading(false);
        }
      };
    
      return (
        <div className="sign-up-form">
          <form onSubmit={handleSubmit}>
            <h2>Insert your one time code received at {challenge_target_label}</h2>
            <div className="form-group">
              <label>Code:</label>
              <input maxLength={code_length} type="text" value={code} onChange={(e) => setCode(e.target.value)} required />
            </div>
            {error && <div className="error">{error}</div>}
            {isLoading && <div className="warning">Sending request...</div>}
            <button type="submit">Sign Up</button>
          </form>
        </div>
      );
    };
    
  4. Créez un fichier src/pages/signup/SignUpCompleted.tsx, puis ajoutez le code suivant :

    import React from 'react';
    import { Link } from 'react-router-dom';
    
    export const SignUpCompleted: React.FC = () => {
      return (
        <div className="sign-up-completed">
          <h2>Sign Up Completed</h2>
          <p>Your sign-up process is complete. You can now log in.</p>
          <Link to="/signin" className="login-link">Go to Login</Link>
        </div>
      );
    };
    

    Cette page affiche un message de réussite et un bouton pour que l’utilisateur accède à la page de connexion une fois qu’il s’est inscrit correctement.

  5. Ouvrez le fichier src/App.tsx, puis remplacez son contenu par le code suivant :

    import React from "react";
    import { BrowserRouter, Link } from "react-router-dom";
    import "./App.css";
    import { AppRoutes } from "./AppRoutes";
    
    function App() {
      return (
        <div className="App">
          <BrowserRouter>
            <header>
              <nav>
                <ul>
                  <li>
                    <Link to="/signup">Sign Up</Link>
                  </li>
                  <li>
                    <Link to="/signin">Sign In</Link>
                  </li>
                  <li>
                    <Link to="/reset">Reset Password</Link>
                  </li>
                </ul>
              </nav>
            </header>
            <AppRoutes />
          </BrowserRouter>
        </div>
      );
    }
    
    export default App;
    
  6. Pour afficher correctement l’application React :

    1. Ouvrez le fichier src/App.css, puis ajoutez la propriété suivante dans la classe App-header :

      min-height: 100vh;
      
    2. Ouvrez le fichier src/Index.css, puis remplacez son contenu par du code de src/index.css

Ajouter des itinéraires d’application

Créez un fichier appelé src/AppRoutes.tsx, puis ajoutez le code suivant :

import { Route, Routes } from "react-router-dom";
import { SignUp } from "./pages/SignUp/SignUp";
import { SignUpChallenge } from "./pages/SignUp/SignUpChallenge";
import { SignUpCompleted } from "./pages/SignUp/SignUpCompleted";

export const AppRoutes = () => {
  return (
    <Routes>
      <Route path="/" element={<SignUp />} />
      <Route path="/signup" element={<SignUp />} />
      <Route path="/signup/challenge" element={<SignUpChallenge />} />
      <Route path="/signup/completed" element={<SignUpCompleted />} />
   
    </Routes>
  );
};

À ce stade, votre application React peut envoyer des demandes d’inscription à l’API d’authentification native, mais nous devons configurer le serveur proxy CORS pour gérer les en-têtes CORS.

Étape suivante