Compartir a través de


Tutorial: Registro de usuarios en una aplicación de página única de React mediante la autenticación nativa (versión preliminar)

Se aplica a: Círculo verde con un símbolo de marca de verificación blanca que indica que el siguiente contenido se aplica a los inquilinos externos. Inquilinos externos (más información)

En este tutorial, aprenderá a crear una aplicación de página única de React que registra a los usuarios mediante la autenticación nativa.

En este tutorial, harás lo siguiente:

  • Cree un proyecto de React.
  • Agregue componentes de interfaz de usuario de la aplicación.
  • Configure el proyecto para registrarse con el nombre de usuario (correo electrónico) y la contraseña.

Prerrequisitos

Creación de un proyecto de React e instalación de dependencias

En una ubicación que prefiera en el equipo, ejecute los siguientes comandos para crear un nuevo proyecto de React con el nombre reactspa, vaya a la carpeta del proyecto y, a continuación, instale paquetes:

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

Adición de un archivo de configuración para la aplicación

Cree un archivo denominado src/config.jsy agregue el código siguiente:

// 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`,
}
  • Busque el Enter_the_Application_Id_Herevalor y reemplácelo por el Id. de aplicación(clientId) de la aplicación que registró en el Centro de administración de Microsoft Entra.

  • El BASE_API_URL apunta a un servidor proxy de uso compartido de recursos entre orígenes (CORS), que configuraremos más adelante en esta serie. La API de autenticación nativa no admite CORS, por lo que configuramos un servidor proxy CORS entre react SPA y la API de autenticación nativa para administrar los encabezados de CORS.

Configuración de la aplicación React para llamar a la API de autenticación nativa y controlar la respuesta

Para completar un flujo de autenticación, como un flujo de registro, con las API de autenticación nativas, la aplicación realiza una llamada y gestiona la respuesta. Por ejemplo, la aplicación inicia un flujo de registro y espera una respuesta y envía atributos de usuario y espera de nuevo hasta que el usuario se registre correctamente.

Configuración de la llamada de cliente a la API de autenticación nativa

En esta sección, definirá cómo realizar llamadas a la autenticación nativa y controlar las respuestas:

  1. Cree una carpeta denominada cliente en el src.

  2. Cree un archivo denominado scr/client/RequestClient.tsy agregue el siguiente fragmento de código:

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

    Este código define cómo realiza la aplicación llamadas a la API de autenticación nativa y controla las respuestas. Siempre que la aplicación necesite iniciar un flujo de autenticación, usa la función postRequest especificando la dirección URL y los datos de carga.

Definición de tipos de llamadas que realiza la aplicación a la API de autenticación nativa

Durante el flujo de registro, la aplicación realiza varias llamadas a la API de autenticación nativa.

Para definir estas llamadas, cree un archivo denominado scr/client/RequestTypes.tsy agregue el siguiente fragmento de código:

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

Definición del tipo de respuestas que la aplicación recibe de la API de autenticación nativa

Para definir el tipo de respuestas que la aplicación puede recibir de la API de autenticación nativa para la operación de registro, cree un archivo denominado src/client/ResponseTypes.tsy agregue el siguiente fragmento de código:

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

Procesamiento de las solicitudes de registro

En esta sección, agregará código que procesa las solicitudes del flujo de registro. Algunos ejemplos de estas solicitudes son iniciar un flujo de registro, seleccionar un método de autenticación y enviar un código de acceso de un solo uso.

Para ello, cree un archivo denominado src/client/SignUpService.tsy agregue el siguiente fragmento de código:

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 propiedad challenge_type muestra los métodos de autenticación que admite la aplicación cliente. Esta aplicación utiliza el correo electrónico con contraseña para iniciar sesión, por lo que el valor del tipo de desafío es redirección de contraseña fuera de banda. Lea más sobre los tipos de desafío .

Creación de componentes de interfaz de usuario

Esta aplicación recopila detalles del usuario, como el nombre, el nombre de usuario (correo electrónico) y la contraseña y un código de acceso de un solo uso del usuario. Así, la aplicación debe tener un formulario de registro y de recopilación de código de acceso de un solo uso.

  1. Cree una carpeta denominada /pages/SignUp en la carpeta src.

  2. Para crear, mostrar y enviar el formulario de registro, cree un archivo src/pages/SignUp/SignUp.tsxy agregue el código siguiente:

        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. Para crear, mostrar y enviar el formulario de código de acceso de un solo uso, cree un archivo src/pages/signup/SignUpChallenge.tsxy agregue el código siguiente:

    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. Cree un archivo src/pages/signup/SignUpCompleted.tsxy agregue el código siguiente:

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

    En esta página se muestra un mensaje de éxito y un botón para llevar al usuario a la página de inicio de sesión después de registrarse con éxito.

  5. Abra el archivo src/App.tsx y reemplace su contenido por el código siguiente:

    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. Para mostrar la aplicación React correctamente:

    1. Abra el archivo src/App.css y agregue la siguiente propiedad en la clase App-header:

      min-height: 100vh;
      
    2. Abra el archivo src/Index.css de y reemplace su contenido por código de src/index.css

Adición de rutas de aplicación

Cree un archivo denominado src/AppRoutes.tsxy agregue el código siguiente:

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

En este momento, la aplicación React puede enviar solicitudes de registro a la API de autenticación nativa, pero es necesario configurar el servidor proxy CORS para administrar los encabezados de CORS.

Paso siguiente