Аутентификация в React — это просто

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


Аутентификация — это одна из тех вещей, которые зачастую требуют от нас гораздо больше усилий, чем нам хотелось бы.

Чтобы реализовать аутентификацию, приходится заново разбираться в темах, о которых вы не вспоминали с тех пор, как в последний раз делали ее для вашего приложения. Ведь эта область очень быстро развивается, а это означает, что за прошедшее с тех пор время появилась целая куча всего нового: новые угрозы, новые решения и обновления ранее используемых вами инструментов, из-за которых вам придется часами копаться в документации и ваших прошлых проектах.

В этом руководстве мы рассмотрим другой подход к аутентификации (а также управлению доступом, SSO и т.д.) в React-приложениях.

Вместо того чтобы использовать статическую библиотеку, которую нужно постоянно обновлять и заново вникать в нее всякий раз, когда вы хотите реализовать где-нибудь аутентификацию, мы будем использовать сервис, который обновляется автоматически и является упрощенной альтернативой Auth0, Okta и их аналогов.

Github-репозиторий примера: github.com/userfront/react-example

Аутентификация в React


Обычно аутентификации в React-приложении выглядит следующим образом: приложение делает запрос к серверу аутентификации, который возвращает токен доступа (access token), затем этот токен сохраняется в браузере и может быть использован при последующих запросах к вашему серверу (или другим серверам, если это необходимо).

Будь то стандартная аутентификация по логину (электронной почте) и паролю или через магические ссылки (magic links) и технологии единого входа (SSO) такие как Google, Azure и Facebook, мы хотим, чтобы наше React-приложение отправляло первоначальный запрос на сервер аутентификации и чтобы этот сервер, в свою очередь, генерировал токен.

Задачи React

  • Отправка первоначального запроса на сервер аутентификации
  • Получение и хранение токена доступа
  • Отправка токена доступа на ваш сервер при каждом последующем запросе.


JWT (JSON Web Tokens)


JSON Web Tokens (JWT) — это компактные и безопасные токены, которые можно использовать для аутентификации и управления доступом в React-приложениях.

Каждый JWT содержит в себе простой JSON-объект, называемый «полезной нагрузкой» (payload), и подписывается специальным образом, чтобы ваш сервер мог удостовериться в подлинности его полезной нагрузки.

Важно отметить, что эта полезная нагрузка JWT может быть прочитана кем угодно: и вашим React-приложением, и любыми третьими лицами, которые видят токен.

Токен доступа JWT выглядит следующим образом:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjRjMDUxZTg3LTU2ZWEtNGUzNC05ZmE3LThkYWNkOThkOWUzMyJ9.eyJtb2RlIjoidGVzdCIsInRlbmFudElkIjoiZGVtbzEyMzQiLCJ1c2VySWQiOjEsInVzZXJVdWlkIjoiNGU2ODgwZTMtZDU0YS00NTNlLTgwMDQtMmJmMGNjNWM1MmY2IiwiaXNDb25maXJtZWQiOnRydWUsImF1dGhvcml6YXRpb24iOnsiZGVtbzEyMzQiOnsicm9sZXMiOlsiY29udHJpYnV0b3IiLCJ2aWV3ZXIiLCJhZG1pbiJdfX0sImlzcyI6InVzZXJmcm9udCIsInNlc3Npb25JZCI6IjNlYTUxOTNjLWE3ZWEtNGM1Zi05ZmRlLTZhZjk0ZTgyNGQ3NCIsImlhdCI6MTY1ODUxNzc2OSwiZXhwIjoxNjU5MTIyNTY5fQ.LB7IyP09G6f64Ho0CdjA8xDe-s7KAjJGPe5lC9GRwSrWHZSnvmoMb1HTRkrO1oabDcpMOvDmyjjkK0Nb-0RoK-bKX3i5PPtvKGu18VKgShxgg0bXYsA-HRrh8bsOExXhKO_I7gS7GVGCMDlSlbGFDRVdtqSqv1rfKj0pRP9PScFjuB9bbdGlmJXbQSw8V_zRxPYEUOnoTqW7-vfyn3DONbo0cElm1mRVMZzK1W8-Kpg6MRTt7nlMj60ysoBktM4w6KdOlDTmlyLzy6kjkqSylT_pDk0ALWW2tCRV8qZcrzJhWu-g8x6MIEUCo8TArELdl6aUGLdItVi-WmQCZNwajQ

Полезной нагрузкой в этом JWT является средняя часть (отделенная точками):

eyJtb2RlIjoidGVzdCIsInRlbmFudElkIjoiZGVtbzEyMzQiLCJ1c2VySWQiOjEsInVzZXJVdWlkIjoiNGU2ODgwZTMtZDU0YS00NTNlLTgwMDQtMmJmMGNjNWM1MmY2IiwiaXNDb25maXJtZWQiOnRydWUsImF1dGhvcml6YXRpb24iOnsiZGVtbzEyMzQiOnsicm9sZXMiOlsiY29udHJpYnV0b3IiLCJ2aWV3ZXIiLCJhZG1pbiJdfX0sImlzcyI6InVzZXJmcm9udCIsInNlc3Npb25JZCI6IjNlYTUxOTNjLWE3ZWEtNGM1Zi05ZmRlLTZhZjk0ZTgyNGQ3NCIsImlhdCI6MTY1ODUxNzc2OSwiZXhwIjoxNjU5MTIyNTY5fQ


JSON-объект в полезной нагрузке JWT зашифрован в base64:

JSON.parse(atob("eyJtb2RlIjoidGVzdCIsInRlbmFudElkIjoiZGVtbzEyMzQiLCJ1c2VySWQiOjEsInVzZXJVdWlkIjoiNGU2ODgwZTMtZDU0YS00NTNlLTgwMDQtMmJmMGNjNWM1MmY2IiwiaXNDb25maXJtZWQiOnRydWUsImF1dGhvcml6YXRpb24iOnsiZGVtbzEyMzQiOnsicm9sZXMiOlsiY29udHJpYnV0b3IiLCJ2aWV3ZXIiLCJhZG1pbiJdfX0sImlzcyI6InVzZXJmcm9udCIsInNlc3Npb25JZCI6IjNlYTUxOTNjLWE3ZWEtNGM1Zi05ZmRlLTZhZjk0ZTgyNGQ3NCIsImlhdCI6MTY1ODUxNzc2OSwiZXhwIjoxNjU5MTIyNTY5fQ"));


/**
* {
*   "userId": 1,
*   "userUuid": "4e6880e3-d54a-453e-8004-2bf0cc5c52f6",
*   "isEmailConfirmed": true,
*   ...
* }
*/


Токены доступа JWT


Благодаря современной криптосистеме RSA сервер аутентификации способен генерировать защищенные JWT, которые любая другая машина может прочитать и проверить с помощью публичного (открытого) ключа.

Ваш бэкенд-сервер может подтвердить подлинность токена доступа JWT, сверив его с публичным ключом.

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

JWT в React-приложении

  • Ваше React-приложение запрашивает токен доступа JWT всякий раз, когда пользователь хочет войти в систему.
  • Сервер аутентификации генерирует токен доступа JWT на основе приватного (закрытого ключа) и затем отправляет его обратно в React-приложение.
  • React-приложение хранит этот токен доступа JWT и отправляет его на бэкенд-сервер всякий раз, когда пользователю необходимо сделать запрос.
  • Ваш бэкенд-сервер проверяет токен доступа JWT с помощью публичного ключа, а затем считывает полезную нагрузку, чтобы определить, какой пользователь выполняет запрос.

Примечание
Все эти шаги на бумаге выглядят достаточно просто, но каждый из них имеет свой набор «подводных камней», на которые вы обязательно наткнетесь, когда вам нужно будет реализовать их на практике и обеспечить безопасность. Особенно когда с течением времени появляются новые источники угроз и требуется исправление или поддержка новых платформ — вам всегда будет нужно совершать какие-нибудь действия, направленные на обеспечение безопасности.

Userfront устраняет всю эту мороку с авторизацией в React-приложениях


Userfront — это фреймворк, который позволяет вам абстрагироваться от сложностей авторизации. Это полнофункциональное решение, которое автоматически обновляется.

Это значительно упрощает работу с аутентификацией в React-приложениях и, что, пожалуй, наиболее важно, берет на себя заботу об актуальности всех протоколов аутентификации.

Настройка аутентификации в React


Теперь мы рассмотрим процесс создания всех основных аспектов аутентификации в React-приложении.

Для создания приложения мы будем использовать Create React App, а для маршрутизации на стороне клиента — React Router.

GitHub репозиторий примера: github.com/userfront/react-example

Оборот токенов доступа JWT


На высоком уровне ответственность React по аутентификации заключается в следующем:

  • Отправка первоначального запроса в Userfront для получения токена доступа JWT. Это делают формы регистрации и логина в систему.
  • Отправка токена доступа JWT на ваш сервер при каждом последующем запросе.



Установка Create React App


Чтобы начать работу с React, нам нужно установить Create React App и React Router.

npx create-react-app my-app
cd my-app
npm install react-router-dom --save
npm start


Теперь наше React-приложение доступно по адресу localhost:3000.

Как сказано на превью, мы можем начать с редактирования файла src/App.js.

Превью


Маршрутизация


Мы создадим небольшое приложение с маршрутизацией. Этого нам будет достаточно для начала добавления аутентификации.
Маршрут Описание
/ Главная страница
/login Страница входа в систему
/reset Страница сброса пароля
/dashboard Информационная панель, только для вошедших в систему пользователей

Замените содержимое файла src/App.js на следующий код (который был взят из руководства React Router):

// src/App.js

import React from "react";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";

export default function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/login">Login</Link>
            </li>
            <li>
              <Link to="/reset">Reset</Link>
            </li>
            <li>
              <Link to="/dashboard">Dashboard</Link>
            </li>
          </ul>
        </nav>

        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/login" element={<Login />} />
          <Route path="/reset" element={<PasswordReset />} />
          <Route path="/dashboard" element={<DefaultLayout />} />
        </Routes>
      </div>
    </Router>
  );
}

function Home() {
  return <h2>Home</h2>;
}

function Login() {
  return <h2>Login</h2>;
}

function PasswordReset() {
  return <h2>Password Reset</h2>;
}

function Dashboard() {
  return <h2>Dashboard</h2>;
}

Мы создали маршруты и готовы заняться непосредственно аутентификацией.

Превью


Регистрация, логин и сброс пароля


Начнем мы с того, что добавим на главную страницу форму для регистрации.

Вы можете найти инструкцию созданию формы регистрации в разделе Toolkit на вашей панели управления.

Она будет выглядеть следующим образом:

Userfront Toolkit

Оставьте Toolkit открытым в другой вкладке браузера, чтобы позже добавить формы входа в систему и сброса пароля.

Следуя инструкциям, установите Userfront Toolkit для React с помощью следующей команды:

npm install @userfront/toolkit --save
npm start


Затем добавьте форму регистрации на главную страницу, импортировав и инициализировав Userfront, и обновив функцию Home(), чтобы она отображала форму:

// src/App.js

import React from "react";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import Userfront, { SignupForm } from "@userfront/toolkit/react";

Userfront.init("demo1234");

export default function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/login">Login</Link>
            </li>
            <li>
              <Link to="/reset">Reset</Link>
            </li>
            <li>
              <Link to="/dashboard">Dashboard</Link>
            </li>
          </ul>
        </nav>

        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/login" element={<Login />} />
          <Route path="/reset" element={<PasswordReset />} />
          <Route path="/dashboard" element={<DefaultLayout />} />
        </Routes>
      </div>
    </Router>
  );
}

function Home() {
  return (
    <div>
      <h2>Home</h2>
      <SignupForm />
    </div>
  );
}

function Login() {
  return <h2>Login</h2>;
}

function PasswordReset() {
  return <h2>Password Reset</h2>;
}

function Dashboard() {
  return <h2>Dashboard</h2>;
}

Теперь форма регистрации размещена на главной странице. Давайте попробуем зарегистрировать нового пользователя.

Превью


Тестовый режим
По умолчанию форма находится в «Тестовом режиме» (Test mode), позволяющем создавать учетные данные пользователей в тестовой среде, которую можно отдельно просмотреть на панели управления Userfront.



Формы логина и сброса пароля мы можем добавить тем же способом, что и форму регистрации. Чтобы позволить пользователю выйти из системы, мы можем вызвать встроенный метод Userfront.logout.

Нам нужно сделать следующие изменения в файле src/App.js:
  • Добавить в метод Login() возврат формы входа в систему.
  • Добавить в метод PasswordReset() возврат формы сброса пароля.
  • Добавить в метод Dashboard() отображение данных пользователя и кнопки выхода из системы.

Обратите внимание на toolId для каждой формы в вашем Toolkit. Приведенные в данном примере кода toolId не подойдут для вашего приложения.

// src/App.js

import React from "react";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import Userfront, {
  SignupForm,
  LoginForm,
  PasswordResetForm
} from "@userfront/toolkit/react";

Userfront.init("demo1234");

export default function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/login">Login</Link>
            </li>
            <li>
              <Link to="/reset">Reset</Link>
            </li>
            <li>
              <Link to="/dashboard">Dashboard</Link>
            </li>
          </ul>
        </nav>

        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/login" element={<Login />} />
          <Route path="/reset" element={<PasswordReset />} />
          <Route path="/dashboard" element={<DefaultLayout />} />
        </Routes>
      </div>
    </Router>
  );
}

function Home() {
  return (
    <div>
      <h2>Home</h2>
      <SignupForm />
    </div>
  );
}

function Login() {
  return (
    <div>
      <h2>Login</h2>
      <LoginForm />
    </div>
  );
}

function PasswordReset() {
  return (
    <div>
      <h2>Password Reset</h2>
      <PasswordResetForm />
    </div>
  );
}

function Dashboard() {
  const userData = JSON.stringify(Userfront.user, null, 2);
  return (
    <div>
      <h2>Dashboard</h2>
      <pre>{userData}</pre>
      <button onClick={Userfront.logout}>Logout</button>
    </div>
  );
}

На этом этапе регистрация, вход в систему и сброс пароля должны уже быть вполне работоспособны. Следует отметить, что форма входа на странице /login будет автоматически перенаправлять на страницу /dashboard, если вы уже вошли в систему.

Теперь пользователи могут регистрироваться, логиниться, разлогиниваться и сбрасывать пароль.

Превью


Защищенный маршрут в React


Мы не хотим, чтобы пользователи могли просматривать информационную панель, если они не вошли в систему. Это называется защитой маршрута.

Если пользователь не вошел в систему, но пытается посетить /dashboard, мы можем перенаправить его на экран входа в систему.

Чтобы это реализовать мы можем обернуть компонент <DefaultLayout /> компонентом , который проверяет, вошел ли пользователь в систему. Если пользователь залогинился, его токен доступа будет доступен в виде Userfront.tokens.accessToken, поэтому нам достаточно просто проверить его наличие.

Для перенаправления браузера в случае отсутствия токена доступа компонент RequireAuth использует Navigate и useLocation из React Router.

// src/App.js

import React from "react";
import {
  BrowserRouter as Router,
  Routes,
  Route,
  Link,
  Navigate,
  useLocation,
} from "react-router-dom";
import Userfront, {
  SignupForm,
  LoginForm,
  PasswordResetForm
} from "@userfront/toolkit/react";

export default function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/login">Login</Link>
            </li>
            <li>
              <Link to="/reset">Reset</Link>
            </li>
            <li>
              <Link to="/dashboard">Dashboard</Link>
            </li>
          </ul>
        </nav>

        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/login" element={<Login />} />
          <Route path="/reset" element={<PasswordReset />} />
          <Route
            path="/dashboard"
            element={
              <RequireAuth>
                <DefaultLayout />
              </RequireAuth>
            }
          />
        </Routes>
      </div>
    </Router>
  );
}

function Home() {
  return (
    <div>
      <h2>Home</h2>
      <SignupForm />
    </div>
  );
}

function Login() {
  return (
    <div>
      <h2>Login</h2>
      <LoginForm />
    </div>
  );
}

function PasswordReset() {
  return (
    <div>
      <h2>Password Reset</h2>
      <PasswordResetForm />
    </div>
  );
}

function Dashboard() {
  const userData = JSON.stringify(Userfront.user, null, 2);
  return (
    <div>
      <h2>Dashboard</h2>
      <pre>{userData}</pre>
      <button onClick={Userfront.logout}>Logout</button>
    </div>
  );
}

function RequireAuth({ children }) {
  let location = useLocation();
  if (!Userfront.tokens.accessToken) {
    // Redirect to the /login page
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

Таким образом, если пользователь вошел в систему, он может просматривать информационную панель. А если пользователь не залогинился, он будет перенаправлен на страницу входа в систему.

Теперь у нас есть веб-приложение с готовыми функциями регистрации, входа в систему, выхода из нее, сброса пароля и защищенным маршрутом.

Превью


Аутентификация через API в React


Как мы уже видели выше, на фронтенде при входе пользователя в систему создается токен доступа Userfront.tokens.accessToken. Это токен доступа JWT, который также можно использовать на бэкенде для защиты конечных точек API.

Библиотек для чтения и проверки JWT достаточно большое количество практически на каждом языке. В списке ниже приведены некоторые популярные библиотеки для работы с JWT.

JWT-библиотеки для:
  • Node.js
  • Python
  • Java
  • PHP
  • .NET


Ваше React-приложение может передавать токен доступа JWT в виде Bearer-токена внутри заголовка Authorization. Например:

async function getInfo() {
  const res = await window.fetch("/your-endpoint", {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${Userfront.tokens.accessToken}`,
    },
  });

  console.log(res);
}

getInfo();

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

Ниже приведен пример Node.js middleware для чтения и проверки токена доступа JWT:

// Пример Node.js (Express.js)

const jwt = require("jsonwebtoken");

function authenticateToken(req, res, next) {
  // Считываем токен доступа JWT из заголовка запроса
  const authHeader = req.headers["authorization"];
  const token = authHeader && authHeader.split(" ")[1];
  if (token == null) return res.sendStatus(401); // Возвращаем 401 при отсутствии токена

  // Проверяем токен с помощью публичного ключа Userfront
  jwt.verify(token, process.env.USERFRONT_PUBLIC_KEY, (err, auth) => {
    if (err) return res.sendStatus(403); // Возвращаем 403 при непройденной верификации
    req.auth = auth;
    next();
  });
}

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

console.log(req.auth);

// =>
{
  mode: 'test',
  tenantId: 'demo1234',
  userId: 1,
  userUuid: 'ab53dbdc-bb1a-4d4d-9edf-683a6ca3f609',
  isEmailConfirmed: false,
  authorization: {
    demo1234: {
      roles: ["admin"]
    },
  },
  sessionId: '35d0bf4a-912c-4429-9886-cd65a4844a4f',
  iat: 1614114057,
  exp: 1616706057
}

На основе этой информации можно выполнить дополнительные проверки или использовать userId или userUuid для поиска данных, связанных с пользователем.

Например, если необходимо сделать маршрут доступным только пользователям-администраторам, можно проверять объект authorization из валидного токена доступа:

// Пример Node.js (Express.js)

app.get("/users", (req, res) => {
  const authorization = req.auth.authorization["demo1234"] || {};

  if (authorization.roles.includes("admin")) {
    // Разрешаем доступ
  } else {
    // Запрещаем доступ
  }
});

React SSO (технологии единого входа)


Также мы можем добавить в React-приложение социальных поставщиков идентификационных данных, таких как Google, Facebook и LinkedIn, или бизнес поставщиков идентификации, таких как Azure AD, Office365 и т.д.

Для этого необходимо создать приложение с поставщиком идентификации (например, Google), а затем добавить учетные данные SSO в панель управления Userfront. В результате мы получаем модифицированную систему логина.

Для реализации Single Sign On с помощью этого подхода не требуется никакого дополнительного кода: можно добавлять и удалять поставщиеов, не изменяя формы или способ обработки токенов доступа JWT.

Заключение


Добавление аутентификации и контроля доступа в React-приложение не обязательно должно быть сложной задачей. И этап настройки, и, что еще важнее, сопровождение в течение долгого времени — все это можно переложить на современные платформы, такие как Userfront.

JSON Web Tokens позволяет четко отделить слой генерации токенов от остальной части приложения, что делает его более понятным и модульным. Такая архитектура также позволяет сосредоточить усилия на самом приложении, где вы, скорее всего, создадите гораздо больше пользы для себя и своих клиентов.

Более подробную информацию о добавлении аутентификации в React-приложение можно найти в руководстве Userfront, где описано все: от настройки форм аутентификации до документации по API, примеры репозиториев, работа с различными языками и фреймворками и т.д.

image
Источник: https://habr.com/ru/companies/otus/articles/759222/


Интересные статьи

Интересные статьи

JP Camara, главный инженер Wealthbox в своём блоге поделился интересным опытом ускорения TanStack Table — новой версии React-библиотеки для создания функциональных таблиц — аж до 10 мс. Делимся с вами...
Всем здравствуйте. Ниже будет приведен пример написания PWA приложения готового для использования как в браузере, так и на компьютере с ОС Windows. Используется язык программирования Rust и фреймворк ...
Модельная разработка - это метод разработки мобильных приложений, при котором мы изначально ставим задачу в виде типизируемой модели(схемы) TypeScript и GraphQL, на уровне создания тикета в таск-менед...
Сегодня я хотел бы поделиться с Вами, как быстро и просто можно создать приложение для Android с базовыми знаниями HTML CSS и JS. По данному примеру код на Java для Android будет минима...
Продолжаем лаконичную интерпретацию официальной документации Flutter в формате «вопрос-ответ». Вот уже 3-я часть, и она в большей степени будет полезна React Native-разработчикам. В данной интерп...