Добавление аутентификации входа JWT в приложение React

В последнее время одним из надежных способов аутентификации учетных данных для входа в систему является аутентификация JWT. Сегодня мы обсудим, как реализовать JWT-аутентификацию для приложения входа в систему в React. Для оформления я использую Material UI для React. Первоначально я реализовал JWT-аутентификацию через частный API для входа в систему; замените этот API на свой частный API.

Прежде всего, что такое JWT? JWT — это сокращение от JSON Web Token, а аутентификация JWT — это компактный способ, обеспечивающий безопасную передачу информации между сторонами в виде объекта JSON. Данные можно проверить, поскольку они подписаны цифровой подписью с использованием алгоритма HMAC (Hash-based Message Authentication Code).

Именно так работает аутентификация JWT:

Когда вы впервые регистрируетесь в системе, ваш пароль шифруется с помощью алгоритма HMAC и, следовательно, хэшируется. После этого, если вы входите в систему, на основе вашего пароля генерируются два типа токенов, срок действия обоих истекает через некоторое время. Это время задается бэкендом, и по этой причине вам необходимо сохранить эти токены. После этого, если вы попытаетесь сделать любой запрос API, вы отправите токены вместе с заголовком. Если формат вашего заголовка недействителен, то и ваш запрос тоже. Если у вас правильный заголовок, проверяются оба токена — access и refresh.
Если оба маркера недействительны, вы заставляете выйти из системы того, кто пытается отправить этот API-запрос.

Чтобы реализовать аутентификацию JWT во внешнем интерфейсе, необходимо выполнить следующие шаги:

  1. Создайте приложение React App, затем создайте компонент Login с пользовательским вводом и кнопкой отправки и вызовите его из App.js.
  2. Напишите handleSubmit, где вы сохраните пользовательский ввод как состояние, а также токены доступа и обновления в localStorage.
  3. Отправьте запрос API. Проверьте, есть ли заголовок, если нет, то это некорректный запрос. Если заголовок есть, проверьте наличие маркеров доступа и обновления и сопоставьте их со значениями в localStorage.
  4. Условно проверьте коды состояния и при необходимости нажмите Refresh API и Logout API.
  5. Если после проверки всех этих условий оба токена действительны, вы получите ответ.

Теперь, когда вы понимаете, как работает JWT, давайте разработаем код этого рабочего процесса.

Шаг-1: Создание приложения React и компонента входа в систему

Для этого руководства я использую node версии 17 и последнюю версию менеджера пакетов yarn. Создайте приложение react с именем jwt и перейдите в папку jwt:

npx create-react-app jwt
cd jwt/

Войдите в полноэкранный режим Выйти из полноэкранного режима

Вы напишете компонент с именем Login.js. Для стилизации установите Material-UI с помощью yarn:

yarn add @material-ui/core
yarn add @mui/icons-material
Войти в полноэкранный режим Выйти из полноэкранного режима

Внутри компонента Login вам понадобятся два текстовых поля для имени пользователя и пароля, а также кнопка для отправки.


import React, { useState } from "react";
import Button from "@material-ui/core/Button";
import CssBaseline from "@material-ui/core/CssBaseline";
import TextField from "@material-ui/core/TextField";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import Stack from "@material-ui/core/Stack";
import { useStyles } from "../style.js";

import { MuiThemeProvider, createTheme } from "@material-ui/core/styles";

 const theme = createTheme({
    palette: {
      primary: {
        main: "#121212",
      },
    },
  });
export default function Login(props) {
  const classes = useStyles(props);

  return(
      <Grid container className={classes.root} 
       item xs={12} md={5}>
            <CssBaseline />
            <MuiThemeProvider theme={theme}>
            <Grid>
                <Typography component="h1" variant="h5">
                   Sign in
                </Typography>
            <form className={classes.form} noValidate>
                  <TextField
                   label="Username"
                   variant="outlined"
                   color="primary"
                   margin="normal"
                   required
                   fullWidth
                   id="username"
                   name="username"
                  />
                 <TextField
                   label="Password"
                   type={"password"}
                   variant="outlined"
                   margin="normal"
                   required
                   fullWidth
                   id="password"
                   name="password"
                  />
                 <Button
                   type="submit"
                   fullWidth
                   color="primary"
                   variant="contained"
                   className={classes.submit}
                  >
                    Sign In
                </Button>
            </form>
          </Grid>
        </MuiThemeProvider>
    </Grid>
  )
}
Войти в полноэкранный режим Выход из полноэкранного режима

Я отделил коды стилей от этого компонента. Файл style.js выглядит следующим образом:

import { makeStyles } from "@material-ui/core/styles";


const useStyles = makeStyles((theme) => ({
  root: {
    height: "100vh",
    display: "flex",
    flexDirection: "column",
    margin: theme.spacing(12, 20),
    alignItems: "center",
  },
  form: {
    width: "100%",
    marginTop: theme.spacing(1),
  },
  submit: {
    margin: theme.spacing(3, 0, 2),
    backgroundColor: "#121212",
    color: "#fff"
  },

}));
export { useStyles };

Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь вызовите его из App.js следующим образом:

...
function App() {
  return (
    <div className="App">
     <Login/>
    </div>
  );
}
Войти в полноэкранный режим Выйти из полноэкранного режима

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

Шаг-2: Напишите функцию Handle и храните маркеры доступа и обновления

На этом шаге мы получим данные от пользователя и что-то с ними сделаем. Сначала нам нужно сохранить эти данные с помощью состояний React, а затем отправить API POST.

Объявите состояние, в котором будут храниться имя пользователя и пароль:

const [data, setData] = useState({
    username: "",
    password: "",
  });

Войти в полноэкранный режим Выйти из полноэкранного режима

Внутри текстовых полей поместите состояния в свойство value:


         <TextField
              ...
              id="username"
              name="username"
              value={data.username}
              onChange={handleChange}
            />

           <TextField

              id="password"
              name="password"
              value={data.password}
              onChange={handleChange}
            />

Войти в полноэкранный режим Выйти из полноэкранного режима

Нам нужно отслеживать изменение значений с помощью функции-обработчика с именем handleChange . Напишите функцию следующим образом:

const handleChange = (e) => {
    const value = e.target.value;
    setData({
      ...data,
      [e.target.name]: value,
    });
  };
Вход в полноэкранный режим Выход из полноэкранного режима

Вместо того чтобы отдельно выполнять операцию setState, мы сначала проверяем значение цели, то есть текстового ввода. После этого мы распространяем объект начального состояния.

Все операции API мы будем выполнять с помощью Axios. Установите его:

yarn add axios
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Поместите ваш частный API входа в глобальную переменную, например login:

const login = "your login api"

Войти в полноэкранный режим Выйти из полноэкранного режима

Помните, как все текстовые поля и кнопка Sign In были обернуты в тег формы? Нам нужно передать функцию-обработчик для обработки операции отправки. Кроме того, в теле API типа POST требуется отправить имя пользователя и пароль. В нашем случае мы можем отправить весь объект state в теле API. Вся функция handleSubmit будет выглядеть следующим образом:

const handleSubmit = (e) => {
    e.preventDefault();
    const userData = {
      username: data.username,
      password: data.password,
    };
    axios.post(login, userData)
      .then((response) => {
        if (response.status === 200) {

          console.log(response.status);
          console.log(response.data);
        }
      })
      .catch((error) => {
        if (error.response) {
          console.log(error.response);
          console.log("server responded");

        } else if (error.request) {
          console.log("network error");
        } else {
          console.log(error);
        }
      });
  };
Вход в полноэкранный режим Выход из полноэкранного режима

Если вы проверите API входа в систему в Postman, вы увидите на выходе токены доступа и токены обновления.

Мы должны сохранить их в localStorage. Сделайте это внутри следующего блока:

if (response.status === 200) {
           localStorage.setItem("accessToken", response.data["access"]);
          localStorage.setItem("refreshToken", response.data["refresh"]);

         window.location.href = "/home";
        }
Войти в полноэкранный режим Выход из полноэкранного режима

Напишите минимальный компонент домашней страницы, чтобы увидеть изменения:

import React, { useState } from "react";

export default function Home(){
    return(
        <div>this is home page</div>
    )
}
Войдите в полноэкранный режим Выход из полноэкранного режима

О нет! Он находится на странице входа в систему даже после нашего входа. Это потому, что мы не сделали никакого управления для маршрутизации. Сначала установите React Router:

yarn add react-router-dom
Войти в полноэкранный режим Выйдите из полноэкранного режима

А теперь добавьте следующий код в ваш App.js:

import { BrowserRouter, Route, Routes } from "react-router-dom";

import Home from "./components/Home";
function App() {
  return (
    <div className="App">
    <BrowserRouter>
            <Routes>
              <Route path="/" element={<Login />} />
              <Route path="/home" element={<Home />} />
            </Routes>
          </BrowserRouter>
    </div>
  );
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Шаг-3: Проверьте заголовок запроса API

Внутри вашего домашнего компонента прикрепите токены:

const access = localStorage.getItem("accessToken");
console.log("access", access)

const refresh = localStorage.getItem("refreshToken");
console.log("refresh", refresh)

Войти в полноэкранный режим Выйти из полноэкранного режима

Давайте сделаем API-запрос на главной странице. Поместите ваш приватный API в переменную:

const details = "any of your private API"

Войти в полноэкранный режим Выйти из полноэкранного режима

Если вы не отправите заголовок к этому API, вы получите код ошибки 403 (или любой другой, который отправит back-end). Вы не сможете сделать ни одного запроса к API без заголовка. Чтобы отправить заголовок, создайте объектный заголовок в следующем формате:

const header = {
  "Content-Type": "application/json",
  Authorization: `Bearer ${access}`,
};

Вход в полноэкранный режим Выйти из полноэкранного режима

Вы можете проверить в postman следующим образом:

Скажем, ваш API — это запрос get. Вы можете написать его следующим образом, с созданным вами заголовком:

const [data, setData] = useState([]);
  useEffect(() => {
    axios
      .get(details, {
        headers: headers,
      })
      .then((response) => {
        setData(response.data);
      })
      .catch((error) => {
        console.log(error);
      });
  }, [data]);

return <div>this is home page {data}</div>;
Войти в полноэкранный режим Выйти из полноэкранного режима

Шаг-4: Условная проверка кодов состояния и нажатие Refresh API и Logout API при необходимости

В этом разделе мы рассмотрим логическую ветвь «Access token is valid?-No». В двух словах, если вы обнаружите, что оба токена недействительны, отправьте запрос на API выхода из системы. Если недействителен только токен обновления, отправьте запрос к API Refresh Token и обновите токен обновления.

Вот код:

const logout_url = "your logout url"
const refresh_url = "your refresh url"

// inside the catch block of axios
.catch((error) => {
        if (error.response.status === 401) {
          console.log(error.response);

          axios.post(refresh_url, refresh).then((request) => {
            if (request.status === 200) {
              console.log("refresh valid");
              localStorage.setItem("accessToken", request.data["access"]);
            } else if (request.status === 401) {
              console.log("invalid so logout");
              axios.post(logout_url, headers).then((response) => {
                localStorage.setItem("accessToken", "");
                localStorage.setItem("refreshToken", "");
                console.log("access", response.data["access"]);
                console.log("refresh", response.data["refresh"]);

                window.location.href = "/login";
              });
            }
Вход в полноэкранный режим Выход из полноэкранного режима

Шаг 5: Отправить ответ

Если вы выполнили все эти шаги, вы реализовали приложение, которое надежно работает с аутентификацией JWT. Окончательный код можно найти на моем github — https://github.com/Afroza2/React-Apps/tree/jwt .

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

Оцените статью
devanswers.ru
Добавить комментарий