Руководство: Регистрация пользователей в одностраничном приложении React с встроенной аутентификацией (предварительная версия)

Применяется: зеленый круг с символом белой галочки, указывающим, что следующее содержимое применяется к внешним клиентам. Внешние клиенты (дополнительные сведения)

В этом руководстве вы узнаете, как создать одностраничные приложения React, которое регистрирует пользователей с помощью собственной проверки подлинности.

Изучив это руководство, вы:

  • Создайте проект React.
  • Добавьте компоненты пользовательского интерфейса приложения.
  • Настройте проект для регистрации пользователя с помощью имени пользователя (электронной почты) и пароля.

Необходимые условия

Создание проекта React и установка зависимостей

В выбранном расположении на компьютере выполните следующие команды, чтобы создать проект React с именем reactspa, перейдите в папку проекта, а затем установите пакеты:

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

Добавление файла конфигурации для приложения

Создайте файл с именем src/config.js, а затем добавьте следующий код:

// 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`,
}
  • Найдите значение Enter_the_Application_Id_Here и замените его на идентификатор приложения (clientId) приложения, которое вы зарегистрировали в Центре администрирования Microsoft Entra.

  • BASE_API_URL указывает на прокси-сервер общего доступа к ресурсам (CORS), который мы настроим позже в этой серии руководств. API собственной проверки подлинности не поддерживает CORS, поэтому мы настроим прокси-сервер CORS между React SPA и API собственной проверки подлинности для управления заголовками CORS.

Настройка приложения React для вызова собственного API проверки подлинности и обработки ответа

Чтобы завершить поток проверки подлинности, например поток регистрации, с собственными API проверки подлинности, приложение делает вызов dn обрабатывает ответ. Например, приложение инициирует поток регистрации и ожидает ответа, а затем отправляет атрибуты пользователя и ожидает повторно, пока пользователь не будет успешно зарегистрирован.

Настройте вызов клиента к встроенному API проверки подлинности

В этом разделе описано, как осуществлять вызовы к встроенной аутентификации и как обрабатывать ответы:

  1. Создайте папку с именем client в "src".

  2. Создайте файл с именем scr/client/RequestClient.ts, а затем добавьте следующий фрагмент кода:

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

    Этот код определяет, как приложение вызывает собственный API проверки подлинности и обрабатывает ответы. Всякий раз, когда приложению необходимо инициировать поток проверки подлинности, он использует функцию postRequest, указав URL-адрес и полезные данные.

Определение типов вызовов приложения в собственный API проверки подлинности

Во время потока регистрации приложение выполняет несколько вызовов к собственному API проверки подлинности.

Чтобы определить эти вызовы, создайте файл с именем scr/client/RequestTypes.ts, а затем добавьте следующий фрагмент кода:

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

Определите тип ответов, которые приложение получает из собственного API аутентификации.

Чтобы определить тип ответов, которые приложение может получить из собственного API проверки подлинности для операции регистрации, создайте файл с именем src/client/ResponseTypes.ts, а затем добавьте следующий фрагмент кода:

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

Обработка запросов на регистрацию

В этом разделе вы добавите код, обрабатывающий запросы потока регистрации. Примеры этих запросов — запуск потока регистрации, выбор метода проверки подлинности и отправка одноразового секретного кода.

Для этого создайте файл с именем src/client/SignUpService.ts, а затем добавьте следующий фрагмент кода:

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

Свойство challenge_type показывает методы проверки подлинности, поддерживаемые клиентским приложением. Вход в приложение осуществляется с помощью электронной почты и пароля, поэтому значение типа задачи - перенаправление oob пароля. Узнайте больше о типах испытаний.

Создание компонентов пользовательского интерфейса

Это приложение собирает сведения о пользователе, такие как имя, имя пользователя (электронная почта) и пароль и одноразовый секретный код пользователя. Таким образом, приложение должно иметь форму единовременной регистрации и единовременной формы сбора секретных кодов.

  1. Создайте папку с именем /pages/SignUp в папке src.

  2. Чтобы создать, отобразить и отправить форму регистрации, создайте файл src/pages/SignUp/SignUp.tsx, а затем добавьте следующий код:

        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. Чтобы создать, отобразить и отправить одноразовую форму секретного кода, создайте файл src/pages/signup/SignUpChallenge.tsx, а затем добавьте следующий код:

    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. Создайте файл src/pages/signup/SignUpCompleted.tsx, а затем добавьте следующий код:

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

    На этой странице отображается сообщение об успешном выполнении и кнопка для входа пользователя на страницу входа после успешной регистрации.

  5. Откройте файл src/App.tsx, а затем замените его содержимое следующим кодом:

    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. Чтобы правильно отобразить приложение React, выполните следующие действия.

    1. Откройте файл src/App.css, а затем добавьте следующее свойство в класс App-header:

      min-height: 100vh;
      
    2. Откройте файл src/Index.css, а затем замените его содержимое кодом из src/index.css

Добавление маршрутов приложений

Создайте файл с именем src/AppRoutes.tsx, а затем добавьте следующий код:

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

На этом этапе приложение React может отправлять запросы на регистрацию в собственный API проверки подлинности, но нам нужно настроить прокси-сервер CORS для управления заголовками CORS.

Следующий шаг