Bearer-токен — стандартный способ авторизации в современных API. По принципу это тот же «браслет» из главы 7.1, только с двумя важными отличиями от обычного API key: токен выдаёт сервер на каждый сеанс, и у него есть срок жизни.
Как выглядит заголовок
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJBbm5hIiwiZXhwIjoxNzM5ODcwMDAwfQ.X1Ny...
Слово Bearer — это схема авторизации. По смыслу — «кто принёс этот токен (bearer), тот и владелец». Сам токен — всё, что после слова Bearer. Сервер берёт этот кусок и проверяет.
В fetch выглядит так:
const response = await fetch('/api/posts', {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
JWT — самый частый формат Bearer-токена
Сам по себе токен может быть чем угодно — случайной строкой, идентификатором сессии, шифрованной нагрузкой. На практике стандартом стал JWT — JSON Web Token.
JWT — это три куска, разделённых точками:
eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiIxMjMiLCJleHAiOjE3M30 . X1Ny...
↑ ↑ ↑
header payload signature
Каждый из трёх — base64url-кодированный текст (это просто переупакованная строка, не шифрование).
Header — маленький JSON с указанием алгоритма подписи:
{"alg": "HS256", "typ": "JWT"}
Payload — JSON с данными о пользователе и сроке жизни. Самые частые поля (claims):
{
"sub": "12345", // subject — id пользователя
"name": "Anna",
"email": "anna@example.com",
"role": "admin",
"iat": 1739864400, // issued at — UNIX-время выдачи
"exp": 1739868000 // expiration — UNIX-время истечения
}
Signature — криптографическая подпись первых двух частей секретным ключом, который знает только сервер. Если кто-то подменит header или payload — подпись не сойдётся, сервер отвергнет токен.
Главный сюрприз для новичка: JWT не зашифрован
Payload — обычный base64. Любой, у кого в руках токен, может его декодировать и прочитать всё, что внутри. Откройте jwt.io и вставьте любой свой JWT — сразу увидите содержимое.
Защищена только целостность: подменить payload незаметно нельзя (подпись отвалится). А прочитать — можно. Поэтому в JWT не кладут пароли, номера карт и прочее, что не должно быть видно.
Прочитать payload на фронте бывает полезно — например, чтобы достать имя пользователя или его роль без лишнего запроса:
function decodeJwt(token) {
const [, payload] = token.split('.');
return JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
}
const claims = decodeJwt(accessToken);
console.log(claims.name); // "Anna"
console.log(claims.exp); // 1739868000
Но верить этим данным на фронте бессмысленно: пользователь может подменить токен у себя в браузере на что угодно. Использовать — для UI (показать имя, спрятать админскую кнопку). Доверять — только серверу, который проверит подпись.
Срок жизни и refresh
JWT обычно живёт недолго — от 5 минут до 1 часа. Зачем так мало? Если токен утечёт, окно атаки маленькое — через час он сам перестанет работать.
Но просить пользователя логиниться каждый час неудобно. Поэтому при логине обычно выдают два токена:
- access token — короткоживущий JWT, ходит в каждом запросе;
- refresh token — долгоживущий (дни, недели), хранится отдельно, используется только для одного: обменять на новый access.
Когда access протух (сервер вернул 401), клиент идёт по refresh:
POST /api/auth/refresh
Cookie: refreshToken=fF8a2d...
→ {"access_token": "новый-jwt..."}
Получили новый access — повторили исходный запрос. Пользователь ничего не заметил.
Типичный паттерн перехвата 401
async function api(url, options = {}) {
let response = await fetch(url, {
...options,
headers: { ...options.headers, 'Authorization': `Bearer ${accessToken}` },
});
if (response.status === 401) {
// Access протух — пробуем refresh:
accessToken = await refreshAccessToken();
// Повторяем исходный запрос с новым токеном:
response = await fetch(url, {
...options,
headers: { ...options.headers, 'Authorization': `Bearer ${accessToken}` },
});
}
if (!response.ok) throw new ApiError(response.status);
return response.json();
}
В реальных проектах добавляют защиту от бесконечного цикла (если refresh тоже вернул 401 — редирект на логин), очередь параллельных запросов (чтобы один refresh обслужил все), но базовая идея именно такая.
Краткая шпаргалка
- Bearer-токен в заголовке Authorization: Bearer ....
- JWT — три части через точки, base64, не зашифрованы (только подписаны).
- Payload можно прочитать — нельзя подменить незаметно.
- Access живёт минуты-час; refresh — дни-недели.
- На 401 пробуем refresh и повторяем запрос; если refresh упал — на логин.
Куда лучше класть токены — в куки или в localStorage — разбирается в последней главе модуля.