Настройка авторизации в React: полное руководство по JWT и защите маршрутов

23 апреля 2026 г.

Авторизация в React-приложениях чаще всего реализуется через JWT-токены (JSON Web Token). После успешного входа сервер выдаёт токен, клиент хранит его и при каждом запросе к API отправляет в заголовке Authorization. В этом руководстве ты узнаешь, как настроить полный цикл авторизации: от входа до защиты маршрутов и выхода.

Основные понятия JWT

JWT — это открытый стандарт (RFC 7519) для передачи данных между сторонами в виде JSON-объекта. Токен состоит из трёх частей: заголовка, полезной нагрузки и подписи, разделённых точками. После авторизации сервер возвращает JWT, который клиент обязан отправлять при каждом защищённом запросе. В типовой React-схеме токен хранится в localStorage или sessionStorage (либо в httpOnly-куках, если сервер настроен соответствующим образом).

Структура компонентов авторизации

Для удобства всю логику авторизации выносят в отдельный контекст (AuthContext), который предоставляет состояние (user, token, isAuthenticated) и методы login, logout.

AuthContext — глобальное состояние

Контекст позволяет любому компоненту получить доступ к данным пользователя и функциям входа/выхода без пробрасывания пропсов через всё дерево.

Компонент входа (Login)

Форма с полями email/username и пароль. При отправке вызывает API-эндпоинт, получает токен и сохраняет его через контекст.

Защищённый маршрут (PrivateRoute)

Обёртка над Route из React Router, которая проверяет isAuthenticated. Если пользователь не авторизован — перенаправляет на страницу логина.

Настройка авторизации в React по шагам

Рассмотрим практическую реализацию с React Router v6 и функциональными компонентами.

Шаг 1: создание AuthContext

// src/context/AuthContext.jsx
import { createContext, useState, useContext, useEffect } from 'react';

const AuthContext = createContext(null);

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [token, setToken] = useState(localStorage.getItem('token'));

  useEffect(() => {
    if (token) {
      // Опционально: проверить валидность токена
    }
  }, [token]);

  const login = async (email, password) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password })
    });
    const data = await response.json();
    if (data.token) {
      localStorage.setItem('token', data.token);
      setToken(data.token);
      setUser(data.user);
    }
    return data;
  };

  const logout = () => {
    localStorage.removeItem('token');
    setToken(null);
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, token, login, logout, isAuthenticated: !!token }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => useContext(AuthContext);

Шаг 2: функция логина

Компонент Login использует useAuth и перенаправляет после успешного входа:

// src/pages/Login.jsx
import { useState } from 'react';
import { useAuth } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';

export const Login = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const { login } = useAuth();
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    const result = await login(email, password);
    if (result.token) {
      navigate('/dashboard');
    } else {
      alert('Ошибка входа');
    }
  };

  return ( <form onSubmit={handleSubmit}>...</form> );
};

Шаг 3: функция логаута

Вызов logout очищает токен и редиректит на логин:

// src/components/LogoutButton.jsx
import { useAuth } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';

export const LogoutButton = () => {
  const { logout } = useAuth();
  const navigate = useNavigate();

  const handleLogout = () => {
    logout();
    navigate('/login');
  };

  return <button onClick={handleLogout}>Выйти</button>;
};

Шаг 4: защита маршрутов

Создадим компонент PrivateRoute для React Router v6:

// src/components/PrivateRoute.jsx
import { Navigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';

export const PrivateRoute = ({ children }) => {
  const { isAuthenticated } = useAuth();
  return isAuthenticated ? children : <Navigate to="/login" />;
};

Пример использования в App.jsx:

// src/App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { AuthProvider } from './context/AuthContext';
import { PrivateRoute } from './components/PrivateRoute';
import { Login } from './pages/Login';
import { Dashboard } from './pages/Dashboard';

function App() {
  return (
    <BrowserRouter>
      <AuthProvider>
        <Routes>
          <Route path="/login" element={<Login />} />
          <Route path="/dashboard" element={
            <PrivateRoute><Dashboard /></PrivateRoute>
          } />
        </Routes>
      </AuthProvider>
    </BrowserRouter>
  );
}

Шаг 5: добавление токена в запросы

Для автоматического добавления токена во все запросы создай перехватчик (interceptor) для fetch или используй axios:

// src/api/axios.js
import axios from 'axios';

const api = axios.create({
  baseURL: process.env.REACT_APP_API_URL
});

api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

export default api;

Хранение токенов и безопасность

localStorage уязвим для XSS-атак: злоумышленник может внедрить скрипт и украсть токен. Более безопасный способ — хранить токен в httpOnly-куках с флагом Secure и SameSite=Strict. Однако такой подход усложняет клиентскую логику: JavaScript не имеет доступа к куке, и её чтением занимается сервер автоматически. В большинстве внутренних и средних проектов допустимо хранение в localStorage при условии строгой политики CSP (Content Security Policy).

Обработка ошибок и защита от неавторизованных запросов

При получении от API статуса 401 (Unauthorized) рекомендуется автоматически выполнять логаут и перенаправлять пользователя на страницу входа. В axios-перехватчике это выглядит так:

api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      localStorage.removeItem('token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

Частые ошибки при настройке авторизации

  • Неправильный заголовок: сервер ожидает Authorization: Bearer <token>, а отправлен Authorization: Token <token> или без пробела.
  • Отсутствие обработки обновления токена (refresh token): при истечении срока JWT пользователь вылетает. Реализуй механизм refresh-токена или настрой долгоживущие токены (с осторожностью).
  • Сохранение токена только в памяти (useState): после перезагрузки страницы авторизация теряется. Всегда синхронизируй токен с localStorage.
  • Защита только на фронтенде: защищённые маршруты в React — это лишь UX-улучшение. Все критичные данные должны проверяться на сервере.
  • Не учитывается роль/права: если пользователь сменил роль (например, его заблокировали), фронт узнает об этом только после перезагрузки. Добавь периодическую проверку токена или механизм invalidate.

Заключение

Ты настроил авторизацию в React: создал контекст для состояния, реализовал логин/логаут, защитил приватные маршруты и добавил токен в запросы. Для production-проектов дополнительно продумай безопасное хранение токенов, обработку 401 ошибок и refresh-механизм. Описанный паттерн — стандарт для большинства React-приложений, работающих с REST API и JWT.