В последнее время одним из надежных способов аутентификации учетных данных для входа в систему является аутентификация 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 во внешнем интерфейсе, необходимо выполнить следующие шаги:
- Создайте приложение React App, затем создайте компонент Login с пользовательским вводом и кнопкой отправки и вызовите его из App.js.
- Напишите handleSubmit, где вы сохраните пользовательский ввод как состояние, а также токены доступа и обновления в localStorage.
- Отправьте запрос API. Проверьте, есть ли заголовок, если нет, то это некорректный запрос. Если заголовок есть, проверьте наличие маркеров доступа и обновления и сопоставьте их со значениями в localStorage.
- Условно проверьте коды состояния и при необходимости нажмите Refresh API и Logout API.
- Если после проверки всех этих условий оба токена действительны, вы получите ответ.
Теперь, когда вы понимаете, как работает 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. В некоторых случаях бэкенд предоставляет только маркер доступа, поэтому вы можете пропустить всю проверку маркера обновления. Тем не менее, как только вы его реализуете, вам больше не придется думать о безопасности.