Боротьба: Управління токенами доступу та оновлення у веб-додатках

pic

Фото Izzy Park на Unsplash

Останнім часом я заглибився в роботу з токенами доступу та оновлення для автентифікації в моєму додатку. Те, що починалося як проста імплементація, перетворилося на складну гру з безпекою, логікою та крайніми випадками.

Якщо ви вже працювали з автентифікацією, заснованою на токенах, ви точно зрозумієте, про що я. Давайте розглянемо основні проблеми, з якими я стикаюся, та підхід до правильного управління токенами оновлення.

🔐 Налаштування — Що я намагаюся зробити

Ось мета:

  • Токени доступу мають короткий термін дії (5 хвилин).
  • Токени оновлення мають тривалий термін дії (7 днів) і зберігаються як HttpOnly cookies для підвищення безпеки.
  • Коли токен доступу вичерпує свій термін дії, додаток має тихо оновити його за допомогою токена оновлення без переривання користувача.

Здається, просто, правда?

😡 Проблема — Як управляти потоком оновлення

Труднощі не в тому, щоб видавати токени — проблема в тому, коли та як оновлювати токен доступу і де його зберігати.

  • Зберігання токену доступу — зберігання токену в localStorage або sessionStorage працює, але створює ризики XSS. Зберігання в пам'яті (React state) зникає при оновленні сторінки.
  • Таймінг потоку оновлення — чи варто намагатися оновлювати токен перед кожним запитом? Тільки після відповіді 401? Як часто це занадто часто?
  • Обробка помилок — що робити, якщо оновлення не вдалося? Як коректно вивести користувача з системи без зайвих складнощів?

🛠️ Поточний підхід (і боротьба з ним)

Ось поточна логіка, з якою я працюю:

  1. Токен доступу зберігається в пам'яті (React state).
  2. Токен оновлення зберігається як HttpOnly cookie, захищений від JavaScript на стороні клієнта.
  3. Якщо 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. Коли новий браузер потрапляє на сторінку, як я дізнаюся, що не потрібно навіть намагатися отримати токени?

🚧 Що я розглядаю далі

  1. Відновлення стану при завантаженні — при завантаженні додатка автоматично спробувати викликати точку оновлення. Якщо успішно, відновити токен доступу в пам'яті.
useEffect(() => {  
 const rehydrateToken = async () => {  
 try {  
 const res = await axios.post('/auth/refresh');  
 setAccessToken(res.data.accessToken);  
 } catch {  
 logoutUser();  
 }  
 };  
 rehydrateToken();  
}, []);
  1. Запасний варіант для SessionStorage — якщо токен доступу не знайдений в пам'яті, перевірити sessionStorage як резерв (все одно вразливо до XSS, але краще, ніж нічого).
    Оновлення до терміну придатності — Відстежуйте термін придатності токена в стані і оновлюйте його за хвилину до закінчення терміну щоб уникнути відповідей 401.

🔍 Велика картина

Управління токенами здається однією з тих "вже вирішених проблем", поки ви не намагаєтесь це реалізувати. Кожен метод має свої компроміси між безпекою, зручністю використання та підтримуваністю.

Хоча HttpOnly cookies частково вирішують проблему, справжня боротьба полягає в тому, як надійно обробляти короткоживучі токени доступу, не розчаровуючи користувачів.

🤔 Як ви з цим справляєтесь?

Я знаю, що я не один у цій боротьбі. Якщо ви вже знайшли правильний підхід до логіки токенів доступу/оновлення — або хоча б знайшли баланс, який працює для вас — буду радий почути про це.

Не соромтесь залишити коментар або поділитися, як ваш підхід відрізняється!

Перекладено з: The Struggle: Managing Access and Refresh Tokens in Web Apps

Leave a Reply

Your email address will not be published. Required fields are marked *