Почему JWT нужно передавать в заголовках?
Представим, что вы пытаетесь зайти в ночной клуб, а на входе охранник спрашивает ваш документ. Вместо паспорта вы показываете лист с номером телефона. Вряд ли вас пустят, верно? Точно так же серверу нужно знать, кто вы, и для этого он использует JWT. Но вопрос: как лучше передать этот "паспорт" серверу?
JWT нужно передавать в заголовках запросов HTTP, потому что:
- Это стандартный способ авторизации — заголовок
Authorizationиспользуется для передачи токенов. - Это безопасно (по сравнению с передачей в URL или теле запроса).
- Так намного проще интегрироваться с различными API, которые ожидают токен в заголовке.
Синтаксис передачи токена в заголовке Authorization
JWT передаётся в заголовке Authorization в формате:
Authorization: Bearer <ваш_токен>
Обратите внимание на слово
Bearer. Это стандарт, который указывает, что мы используем токен для аутентификации.
Реализация: добавление токена в заголовки запросов
Теперь, давайте взглянем на практическую реализацию. Мы добавим токен в заголовки запросов, чтобы использовать его для авторизации.
Настройка Axios для передачи токена в заголовке
Мы уже использовали Axios в нашем проекте для работы с API-запросами (если вы ещё не знаете Axios, посмотрите официальную документацию). Теперь добавим токен в заголовки всех запросов.
import axios from 'axios';
// Получаем токен из localStorage
const token = localStorage.getItem('jwtToken');
// Создаём экземпляр Axios с базовым URL и заголовком Authorization
const apiClient = axios.create({
baseURL: 'https://api.example.com',
headers: {
Authorization: `Bearer ${token}`, // Добавляем токен в заголовок
},
});
export default apiClient;
Каждый запрос через apiClient теперь будет содержать ваш JWT в заголовке Authorization. Это удобно, особенно если у вас много разных API-запросов.
Динамическое добавление токена через интерсептор
Но есть одна проблема. Что, если пользователь перелогинится, и токен обновится? Код выше не будет знать об этом, потому что токен передаётся только один раз при создании apiClient. Для решения этой проблемы мы можем использовать "интерсепторы" в Axios.
Интерсепторы позволяют динамически добавлять токен перед каждым запросом.
import axios from 'axios';
// Создаём экземпляр Axios
const apiClient = axios.create({
baseURL: 'https://api.example.com',
});
// Добавляем интерсептор для запросов
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('jwtToken'); // Получаем актуальный токен из localStorage
if (token) {
config.headers.Authorization = `Bearer ${token}`; // Добавляем токен в заголовок
}
return config;
}, (error) => {
return Promise.reject(error); // Обрабатываем ошибки
});
export default apiClient;
Теперь токен будет добавляться в заголовки перед каждым запросом, даже если он изменился.
Пример авторизованного запроса
Давайте посмотрим, как мы можем использовать apiClient для выполнения авторизованного запроса. Например, мы хотим получить список защищённых маршрутов:
import apiClient from './apiClient';
async function fetchProtectedData() {
try {
const response = await apiClient.get('/protected-data');
console.log('Защищённые данные:', response.data);
} catch (error) {
console.error('Ошибка при получении данных:', error);
}
}
fetchProtectedData();
Если токен действителен, сервер вернёт защищённые данные. Если токен недействителен или отсутствует, сервер ответит с ошибкой (например, 401 Unauthorized).
Проверка токенов на серверной стороне
Когда сервер получает запрос с токеном, он должен проверить его подлинность. Обычно это делается с помощью проверочного метода в библиотеке для работы с JWT.
Например, на Node.js с библиотекой jsonwebtoken:
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization']; // Получаем заголовок Authorization
const token = authHeader && authHeader.split(' ')[1]; // Извлекаем токен после "Bearer"
if (!token) {
return res.status(401).json({ message: 'Токен отсутствует!' }); // Нет токена? Нет доступа.
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ message: 'Токен недействителен!' }); // Токен просрочен или подделан
}
req.user = user; // Добавляем пользователя в запрос
next(); // Всё хорошо — переходим к следующему middleware
});
}
Теперь сервер будет проверять токены для всех защищённых маршрутов.
Защита маршрутов на клиентской стороне
На стороне клиента нам нужно проверить, есть ли токен, чтобы решить, можно предоставить доступ к защищённым маршрутам или нет. Мы будем использовать React Router для защиты маршрутов.
Создадим компонент ProtectedRoute, который проверяет наличие токена и перенаправляет пользователя на страницу логина, если токена нет.
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
const ProtectedRoute: React.FC = () => {
const token = localStorage.getItem('jwtToken'); // Проверяем наличие токена
return token ? <Outlet /> : <Navigate to="/login" />; // Если токен есть, рендерим дочерний компонент, иначе редирект
};
export default ProtectedRoute;
Теперь обернём защищённые маршруты в компонент ProtectedRoute:
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import LoginPage from './LoginPage';
import Dashboard from './Dashboard';
import ProtectedRoute from './ProtectedRoute';
const App: React.FC = () => {
return (
<Router>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>
</Routes>
</Router>
);
};
export default App;
Теперь страница /dashboard будет доступна только для авторизованных пользователей.
Типичные ошибки и как их избежать
Отсутствие токена в заголовке. Больше всего проблем возникает, когда токен не передаётся (например, пользователь вышел, и токен удалён). Убедитесь, что вы проверяете наличие токена перед отправкой запроса.
Истёкший токен. Если срок действия токена истёк, сервер вернёт ошибку 401. Для решения этой проблемы можно использовать механизм обновления токенов (refresh tokens).
Неправильный формат заголовка. Убедитесь, что заголовок имеет формат
Bearer <token>. ПропускBearerможет привести к ошибке.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ