Фото Izzy Park на Unsplash
Останнім часом я заглибився в роботу з токенами доступу та оновлення для автентифікації в моєму додатку. Те, що починалося як проста імплементація, перетворилося на складну гру з безпекою, логікою та крайніми випадками.
Якщо ви вже працювали з автентифікацією, заснованою на токенах, ви точно зрозумієте, про що я. Давайте розглянемо основні проблеми, з якими я стикаюся, та підхід до правильного управління токенами оновлення.
🔐 Налаштування — Що я намагаюся зробити
Ось мета:
- Токени доступу мають короткий термін дії (5 хвилин).
- Токени оновлення мають тривалий термін дії (7 днів) і зберігаються як HttpOnly cookies для підвищення безпеки.
- Коли токен доступу вичерпує свій термін дії, додаток має тихо оновити його за допомогою токена оновлення без переривання користувача.
Здається, просто, правда?
😡 Проблема — Як управляти потоком оновлення
Труднощі не в тому, щоб видавати токени — проблема в тому, коли та як оновлювати токен доступу і де його зберігати.
- Зберігання токену доступу — зберігання токену в
localStorage
абоsessionStorage
працює, але створює ризики XSS. Зберігання в пам'яті (React state
) зникає при оновленні сторінки. - Таймінг потоку оновлення — чи варто намагатися оновлювати токен перед кожним запитом? Тільки після відповіді
401
? Як часто це занадто часто? - Обробка помилок — що робити, якщо оновлення не вдалося? Як коректно вивести користувача з системи без зайвих складнощів?
🛠️ Поточний підхід (і боротьба з ним)
Ось поточна логіка, з якою я працюю:
- Токен доступу зберігається в пам'яті (React state).
- Токен оновлення зберігається як HttpOnly cookie, захищений від JavaScript на стороні клієнта.
- Якщо API запит повертає
401 Unauthorized
:
- Спробуйте оновити токен доступу, викликавши
/auth/refresh
. - Якщо успішно, повторіть оригінальний запит з новим токеном.
- Якщо оновлення не вдалося, виведіть користувача з системи.
Ось axios інтерсептор, який я використовую:
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const res = await axios.post('/auth/refresh');
const { accessToken } = res.data;
// Зберігаємо в React state
setAccessToken(accessToken);
api.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
return api(originalRequest); // Повторити неуспішний запит
} catch (refreshError) {
console.error('Не вдалося оновити токен:', refreshError);
logoutUser();
}
}
throw error;
}
);
😖 Чому це викликає головний біль
- Оновлення сторінки руйнує стан — якщо сторінка перезавантажується, токен доступу в React state зникає. Тепер потрібно з’ясувати, як знову синхронізувати токен.
- Жонглювання токенами — зберігання токена доступу в localStorage працює, але здається менш безпечним. Пам'ять безпечніша, але вразлива.
- Тихі помилки — якщо токен оновлення прострочений або недійсний, додаток перенаправляє на екран входу, але інколи здається, що він не дає чіткої зворотної реакції.
- Контексти — у мене є кілька Contexts, які обгортають мій додаток для отримання налаштувань користувача та іншої інформації з API. Коли новий браузер потрапляє на сторінку, як я дізнаюся, що не потрібно навіть намагатися отримати токени?
🚧 Що я розглядаю далі
- Відновлення стану при завантаженні — при завантаженні додатка автоматично спробувати викликати точку оновлення. Якщо успішно, відновити токен доступу в пам'яті.
useEffect(() => {
const rehydrateToken = async () => {
try {
const res = await axios.post('/auth/refresh');
setAccessToken(res.data.accessToken);
} catch {
logoutUser();
}
};
rehydrateToken();
}, []);
- Запасний варіант для SessionStorage — якщо токен доступу не знайдений в пам'яті, перевірити
sessionStorage
як резерв (все одно вразливо до XSS, але краще, ніж нічого).
Оновлення до терміну придатності — Відстежуйте термін придатності токена в стані і оновлюйте його за хвилину до закінчення терміну щоб уникнути відповідей401
.
🔍 Велика картина
Управління токенами здається однією з тих "вже вирішених проблем", поки ви не намагаєтесь це реалізувати. Кожен метод має свої компроміси між безпекою, зручністю використання та підтримуваністю.
Хоча HttpOnly cookies частково вирішують проблему, справжня боротьба полягає в тому, як надійно обробляти короткоживучі токени доступу, не розчаровуючи користувачів.
🤔 Як ви з цим справляєтесь?
Я знаю, що я не один у цій боротьбі. Якщо ви вже знайшли правильний підхід до логіки токенів доступу/оновлення — або хоча б знайшли баланс, який працює для вас — буду радий почути про це.
Не соромтесь залишити коментар або поділитися, як ваш підхід відрізняється!
Перекладено з: The Struggle: Managing Access and Refresh Tokens in Web Apps