Выкананне токенав доступе і токенав апдэтаў разам у Node.js
Короткавякучыя токены доступу плюс токены апдэта, якія зміняюцца, у Node.js: працэўванне JWT, дзе хаваць кожны токен, і як зміна токенав апдэта запобегае крадзенню.
Крыніцы: Access Tokens vs Refresh Tokens in Node.js: Why Real Applications Need Both
Калі вы ствараеце свой першы процес аутэнтыкаціі ў Node.js, стандартны падход — апыляцца на адні JSON Web Token (JWT). Пользователь падае свой электронны адрес і пароль, сервер перакантрацуе іх, падпісвае токен і вяртае яго. З таго моменту фронтэнд включае гэты токен у заголовак Authorization практычна ў кожны запит.
Такая налада здаецца прыемной пад час тэставання на localhost. Але як толькі справжнія корыстувальцы пачнаюць апыляцца на дапамогу прыстанку, з’яўляецца структурны напруга.
Установіце тэрмін дзейснасці токена на короткі перыяд, напрыклад 15 хвілін, і корыстуначы будуць выключаны пасля завершэння задачі — можа, калі вони пішчуць каментары або на паўпаце заповнення формы. Якщо ж установіце тэрмін на длігі перыяд, напрыклад 30 дзён, люди застануцца запісанымі, але ў той жа час з’явіцца серьёзная прычына для абавярэння: якщо такі токен будзе вкрадзены, не будзе можлівасці анулювацыя яго без анулювання кожного токена для кожнага корыстунача ў всій системе.
Гэты напружанне межу зручнасцю для корыстуначаў і безпекай якраз і ўсё тое, чырэз што системы промышленага рангу не павяржаюцца на адны токен — яны викорыстоўваюць два: токен доступу і токен апавярэння.
Разберамся, чырэз што існуе такая модель з двума токенамі, якую ролю выпаловае кожны з іх і як правільна ўстановіць яе ў бэкендзе на Node.js.
Асновная проблема аутэнтыфікаціі з адным токенам
Перш чым поясніць, чаму неабходны два токены, корыстна будзе парадоксальная думка пра тое, што самае спачатку робіць JWT унікальнымі: яны ўсё-такі безстановыя.
Калі сервер на Node.js прыме JWT, яму не трэба запускать запит да базы дадзеных, каб пераканацца, чы робота сесіі існуе. Ён проста практыкуе крыптаграфічную падпись токена працою з секрэтным ключам:
Token payload + Secret key = Signature verified?
├── Yes -> Request allowed
└── No -> 401 Unauthorized
Безстановасць і є тым, што робіць API на базе JWT швабрамі та прыемнымі для масштабавання, адже база дадзеных не запытваецца за кожны прыходзячы запит. Але гэта ж якосць мае і недзеянне: адже толькі раз выданы токен, сервер не мае простаг спосабу яго раннія анулювання.
Разглядзім следуючыя падзеі:
- Ваш ўнікальны JWT налаштаваны так, каб яго тэрмін дзейнасці становіў 30 дзён.
- Зловершальнік захопляе яго — можа через атаку типу XSS або перехопленнем трафіку на незахіщанай сетцы.
Аднам з спосабаў рашэння є падтрымкі чорнага списку анульваных токенав у такім інструменте, як Redis, але перагляд гэтага списку праз кожны вызов API фактычна ператварае вашу „безстановую“ систему токенав знову на становую.
Гэта сама проблема, якую прагне рашыць метод двух токенав.
Што такое токены доступу і токены апавяржэння?
Уместо таго, каб адзін токен быў адпаведальны як за звычайны доступ да API, так і за давялівую перзыванню пад час заходжэння, заводкі роздзеляюцца между двума токенамі:
+------------------+-----------------------------+-----------------------------+
| Feature | Access Token | Refresh Token |
+------------------+-----------------------------+-----------------------------+
| Purpose | Authenticate API requests | Obtain new access tokens |
| Lifetime | Short (5 to 15 minutes) | Long (7 to 30 days) |
| Storage | Memory or short-lived state | HttpOnly, Secure Cookie |
| Database lookup | No (purely stateless) | Yes (checked on refresh) |
| Exposure risk | High (sent on every request)| Low (sent only to /refresh) |
+------------------+-----------------------------+-----------------------------+
1. Токен доступу
Складзіце токен даступу як карточку на короткі час. Кліент прыўязвае яго да заголовкаў звычных запытак API — такіх як GET /api/orders або POST /api/profile.
Паколькі ён дзейсны толькі прыбліжна на кароткі час (зазвычай 10–15 хвілін), будзь-які патэнцыяльны наклеп ад вытэкання данных є обмежаны. Сервер можа пераканацца ў яго абоцэнна за дапамогою перагляду падпіса на базе CPU, не чапаючыся да базы дадзеных, што памагае заставіць API рэакtywnым.
2. Токен абнавлення
Токен абнавлення функцыонуе як купон на дзейсны час абнавлення. Ёго ніколі не выкарыстоўваюць у звычных запытках API — яго ўжо задача — весты справу з спецыяльным канцэнтрам, напрыклад POST /api/auth/refresh.
Калі токен даступу з мягкым тэрміном службы выйдзе з силы, кліент тыха вызывае этот маршрут павернення. Сэрвер затым запытае базу дадзеных, каб парадактуваць, чы рачунок пользователя ўсё яшчэ актыўны і мае правы на даступ, пасля чаго выдае новы токен даступу.
Паколькі такі вызыв павернення вырабляецца толькі прыблізна кожныя 15 хвілін, а не пасля кожной взаімадзеі з пользователя, дадагэнне запыту да базы дадзеных практычна не вплывае на прыдатнасць.
Цикл жыцця запыту
Ось як тры стороны — кліент, сэрвер і база дадзеных — взаінаодноўляюць звязак з часам:
Client Server Database
│ │ │
├─────── 1. Login (credentials) ──>│ │
│ ├─── Verify credentials ─────────>│
│ │<── User record valid ───────────┤
│ ├─── Store Refresh Token Hash ───>│
│<────── 2. Return Tokens ─────────┤ │
│ (Access: 15m, Refresh: 7d) │ │
│ │ │
│─────── 3. GET /api/dashboard ───>│ │
│ [Bearer Access Token] ├─── Verify signature (Stateless) │
│<────── 4. 200 OK + Data ─────────┤ │
│ │ │
│ ... 15 minutes pass ... │ │
│ │ │
│─────── 5. GET /api/dashboard ───>│ │
│ [Expired Access Token] ├─── Token expired! │
│<────── 6. 401 Unauthorized ──────┤ │
│ │ │
│─────── 7. POST /api/auth/refresh>│ │
│ [Cookie: Refresh Token] ├─── Check if token in DB ───────>│
│ │<── Token valid & not revoked ───┤
│ ├─── Generate new Access Token │
│<────── 8. Return new Access Token┤ │
│ │ │
│─────── 9. Retry GET /dashboard ─>│ │
│ [New Access Token] ├─── Verify signature │
│<────── 10. 200 OK + Data ────────┤ │
З точкі зору пользователя весь гэты процес павернення не ўвідны. Їх сэсія проста продовжвае працаваць у фоне, без непрыемных паведамленняў „Сэсія выйшла з силы“, якія перарываюць актыўнае вядзенне.
Практычны прыклад з Node.js
Тепер давайце створым работаючую настаўку Express, якая пакажае, як выдаваць, пераглядаць і павернюваць токены.
Пачатакніце з установкі:
npm install express jsonwebtoken cookie-parser dotenv
1. Стварэнне токенаў
Створыце дапаможны модуль, які генеруе оба токаны, кожны з якіх падпісваецца сваёй секрэтной канфігурацыяю:
// tokenService.js
const jwt = require('jsonwebtoken');
const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET || 'access_secret_123';
const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET || 'refresh_secret_456';
function generateAccessToken(userId) {
// Short lifespan: 15 minutes
return jwt.sign({ sub: userId }, ACCESS_TOKEN_SECRET, { expiresIn: '15m' });
}
function generateRefreshToken(userId) {
// Long lifespan: 7 days
return jwt.sign({ sub: userId }, REFRESH_TOKEN_SECRET, { expiresIn: '7d' });
}
module.exports = {
generateAccessToken,
generateRefreshToken,
ACCESS_TOKEN_SECRET,
REFRESH_TOKEN_SECRET
};
Ключовы момент: завжды даўайце токану доступу і токану апавяржэння разныя секрэтныя канфігурацыі. Якщо яны будуць дзеліць адну секрэтную канфігурацыю, хтось можа взяць выканчэўваны токан апавяржэння і выкорыстаць яго там, дзе трэба токан доступу, такім чынам айсцягнуўшыся ад выканчэвання, якое вы хацелі застосаваць.
2. Адрабатка процеса заходжання і зберагчэнне токана апавяржэння
Маршрут заходжання павінен зберагчыць токан апавяржэння — але лепш яго SHA-256 хеш — у вашай базе дадзеных, прыўязаны да таго пользователя, ўпрынцыпе, каб пазней можна было яго знайсці:
// server.js
const express = require('express');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');
const {
generateAccessToken,
generateRefreshToken,
ACCESS_TOKEN_SECRET,
REFRESH_TOKEN_SECRET
} = require('./tokenService');
const app = express();
app.use(express.json());
app.use(cookieParser());
// Simulated database table for refresh tokens
const refreshTokenDatabase = new Set();
app.post('/api/auth/login', async (req, res) => {
const { email, password } = req.body;
// In a real app: verify email and hashed password from DB
const mockUserId = 'usr_987654';
const accessToken = generateAccessToken(mockUserId);
const refreshToken = generateRefreshToken(mockUserId);
// Save the refresh token to our database so we can revoke it later
refreshTokenDatabase.add(refreshToken);
// Send refresh token inside an HttpOnly cookie
res.cookie('refreshToken', refreshToken, {
httpOnly: true, // Prevents JavaScript from reading the cookie (protects from XSS)
secure: process.env.NODE_ENV === 'production', // Requires HTTPS
sameSite: 'strict', // Protects against CSRF attacks
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
});
// Send access token in JSON body for frontend memory storage
res.json({ accessToken });
});
3. Захаванне бізнес-канцэнаў
Функцыя мідлвэра пераказвае токен доступу, які быў выявлен у загалоўку Authorization, прычым разрашае проханне толькі пасля пераканання ў його праваменства:
function authenticateAccessToken(req, res, next) {
const authHeader = req.headers['authorization'];
// The header format is "Bearer <token>"
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token missing' });
}
jwt.verify(token, ACCESS_TOKEN_SECRET, (err, decoded) => {
if (err) {
// 403 signals to the client that the token is invalid or expired
return res.status(403).json({ error: 'Invalid or expired access token' });
}
req.userId = decoded.sub;
next();
});
}
// Protected route
app.get('/api/orders', authenticateAccessToken, (req, res) => {
res.json({
message: `Orders retrieved for user ${req.userId}`,
data: [{ id: 1, item: 'Mechanical Keyboard' }]
});
});
4. Стварэнне маршруту абнавлення
Калі кліент отрымае код 403 пасля запыту на канцэнт /api/orders, ён звертаецца да гэтага канцэнаў, каб атрымаць новы токен доступу:
app.post('/api/auth/refresh', (req, res) => {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) {
return res.status(401).json({ error: 'Refresh token missing' });
}
// Check if token exists in the database
if (!refreshTokenDatabase.has(refreshToken)) {
return res.status(403).json({ error: 'Refresh token revoked or unknown' });
}
jwt.verify(refreshToken, REFRESH_TOKEN_SECRET, (err, decoded) => {
if (err) {
// Remove invalid token from database
refreshTokenDatabase.delete(refreshToken);
return res.status(403).json({ error: 'Refresh token expired' });
}
// Token is valid! Issue a new access token
const newAccessToken = generateAccessToken(decoded.sub);
res.json({ accessToken: newAccessToken });
});
});
5. Выйшчы з абліковання і анулюванне доступу
Выключэнне прабрамы корыстніка ўсё проста: выдалейце токен апавяржэння з вашай базы дадзенаў і ачысціце кукі, якія яго зберагаюць. Токен даступу не трэба спецыяльна обрабоўваць — ён проста выйдзе з часу дэйства за калькі хвілін.
app.post('/api/auth/logout', (req, res) => {
const refreshToken = req.cookies.refreshToken;
if (refreshToken) {
refreshTokenDatabase.delete(refreshToken);
}
res.clearCookie('refreshToken');
res.json({ message: 'Logged out successfully' });
});
Дзе павінны знаходзіцца токены на кліянтскай стороне?
Часта задаваемыя пытанні — дзе фронтэнду следуе зберагаць гэтыя токены.
Частыя памылкі — це збераганне обох токенаў у localStorage.
Проблема заключаецца у тым, што будь-які скрыпт, які запускаецца на вашай сторунцы — незалежна ад таго, чы ён падае з парадуранай залежнасці npm чы ўводзіцца як вбудованы таг — можа прачытаць localStorage за дапамогою простаеў localStorage.getItem('token').
Безпечнейшы падход выглядае так:
- Зберагаюце токен даступу толькі ў памяці. Яго трэба зберагаць у стане роботы вашай аплікацыі — у React Context, Pinia store чы простаей зменнай. Паколькі ён шырока скасоўваецца і зникае як толькі закрываецца вікно, нават успешная інжэкцыя скрыпту стварае толькі маленькі перыяд рызыку.
- Зберагаюце токен апавяржэння ў кукі з атрыбутам
HttpOnly. JavaScript ужо не мае доступу да кукі з атрыбутамHttpOnly. Браузер автоматычна дадае яго кожны раз, калі кліент выкананаў запыт/api/auth/refresh, таму вашы довгавечныя аутентифікацыйныя данні ніколі не трапляюцца ў код, які можа кантролаваць зловершальнік.
Паўнэйшасць безпекі за дапамогою ротацыі токенав апавяржэння
Нават з вышэўскім налаштованнем, простая схема токена апавяржэння мае значныя слабасі: якщо зловмешчыку удастся захапіць токен апавяржэння — чы то з хранення кукі, чы пацікавіўшыся трафікам — ён можа продовжваць ствараць новыя токены доступу пры тым, што токен апавяржэння застаецца чынным, можліва, протягом усіх 7 дзён.
Системы, якія выкарыстоўваюцца у працэсе, закрываюць гэтыя слабасі за дапамогою ротацыі токена апавяржэння.
Пры ротацыі:
- Кожны раз, калі токен апавяржэння викорыстоўваецца для отрымання новага токена доступу, сервер знишчае стары токен апавяржэння і дае абліковы новы.
- Токены доступу і апавяржэння заменяюцца разам, па кожным запиту.
Request 1: Send Refresh Token A -> Receive Access Token 2 + Refresh Token B (Token A invalidated)
Request 2: Send Refresh Token B -> Receive Access Token 3 + Refresh Token C (Token B invalidated)
Автаматычнае выяўленне аблушэння
Ротацыя токена апавяржэння дазволяе выкарыстоўваць моцную захоўну меру пад назвай выяўленне паўтарнага выкарыстоўвання.
Уявіце сцэнарыю, калі зловмеснік успевае скопіяваць Refresh Token A.
- Настоямы корыстувач спачатку выкарыстоўвае
Refresh Token A, і сервер адпавядаеRefresh Token B. - Чырвоны час пазней зловмеснік працяўвае над выкарыстоўванням вже неактуальнага
Refresh Token A. - Сервер пераканальвае сваія записы, бачыць, што
Refresh Token Aвже быў выкарыстоўаны, і пазначае гэта як знак атакі. - У адпаведзьбе ён анулюе весь ланцуг токенав — укладаючы
Refresh Token B— што змушвае як зловмесніка, так і законны корыстувача вернуцца на экран аблогавання.
У працоўку замест таго, каб зловмеснік таямна продаваў свою дзеяльнасць у фоне, система негайна рэагуе і блакуе всім доступ, пакуль не будзе адбыта перапрацавка аблогавання.
Прыгожыя падступы, якіх трэба утрымацца
Зважайце на гэтыя чатыры часта павтараючыся прыгожыя:
- Уключэнне чутлівай інфармацыі ў токэн даступу. Кантэнт JWT кодуецца толькі у формате Base64, а не шифруецца, таму хто бы то ні было, хто яго перехопіць, можа без проблем прачытаць його змест. Пасварды, номеры картак і іншыя прыватныя даны неаважна ўключаць у кантэнт токэна.
- Дазвол лісту з рэфреш-токэнамі нарабляцца без канца. Без столбца з датай заканчэння терміну дыяўнасці і процедуры чысткі, таблыца з рэфреш-токэнамі будзе растаць без меж. Дадзіце поле
expires_atі запланаваце задачу, якая періядычна выдалялаў бы тыповыя ентры.
403. Якщо ваш фронтэнд у якой-то момант выконвае калька паралельных канэктаў /refresh у якой-то меры, логіка ротацыі спрыяе гэта як павторнае выкарыстоўванне токена і завершае сесію. Чыгнуць гэтаму можна, размістыўшы усе выходныя запиты ў чергу пасля аднаго канэкта на апавежэнне.Ситуацыі, калі можа паспрацаваць адзін токен
Гэты падход з двума токенамі ёсць стандартным выборам для сучасных веб- і мобільных кліянтав, але ён не ўсюды є обавязковым:
- Внутршняя пераказка дадзеных між службамі: пры взаімных канэктах машын зазвычай выкарыстоўваюцься процесы апраўлення кліентскіх дакументаў, токены короткага тэрміну дзейнасці або механізмы взаіменнага TLS, якія не выкарыстоўваюць циклы апавежэння.
- Базавыя аплікацыі, выкананыя на сервере: для традыцыйнай аплікацыі, выкананай на серверае — напрыклад, той, якая створана з класычных шаблонаў Express — простая кукуця сэсіі, якая зберагаецца ў памяці, такой як Redis, зазвычай лёгкая ў кераванні і забезпечвае падобную безпеку.
Выбірайце модель access-and-refresh-token тады, калі ствараеце аплікацыі з адной сторанай (React, Vue чыю мобільныя кліенты), якіе вярбуюцца з безстановымі бэкендамі на базе REST чы GraphQL.
Заключэнне
Адчыненне на адны JWT для всього вымушвае робіць выбор, якога не трэба было бы робіць: альбо трываласць сэсій залишаць короткай і раздражваць выкарыстоўчыкаў частымі входамі, альбо трываласць сэсій залишаць дзяўгай і падвергаць ся серйозным рызікам для безпекі.
Раздзелэнне аутентыкацыі на два токены рашае гэты прытвор:
- Токены доступу дазваляюць кожным запытам выконвалівацца шырока і без стану, чым захоўваецца ваша база дадзеных ад постоянных пашукаванняў.
- Токены апавяржэння безпечна керуюць дзейнасцю сесій на далёкі тымчас, даўаючы можлівасць негайна анулюваць захаваныя сесіі, якія сталі атакаванымі.
Інвестыраванне зусіль у правильную наладку обох токенаў прыносіць практычныя выгоды: спакойную роботу для корыстнікаў і систему, якая эфектыва працюе ў разы з абмовамі безпекі, пра якія гаворыцца тут.