Аутентификация в 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-библиотеки для:

Ваше 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/articles/759222/


Комментарии

Добавить комментарий

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