Пастка токенів стала причиною збою на реальному сайті, коли п'ять вкладок, відкритих одним користувачем, одночасно спробували оновити прострочений JWT за ту саму мілісекунду, заваливши бекенд дубльованими запитами на оновлення та миттєво анулювавши сесію. Кожна вкладка розлогінила користувача, довівши, що рішення для оновлення токена лише в одній вкладці більше не є достатнім.

Чому пастка токенів — це важливо

Сучасні односторінкові додатки (SPA) використовують короткострокові токени доступу (access tokens) та довгострокові токени оновлення (refresh tokens). Коли термін дії токена доступу закінчується, клієнт надсилає запит на оновлення, отримує нову пару токенів і повторює оригінальний виклик. Більшість розробників захищають цей процес за допомогою прапорця в пам'яті (наприклад, isRefreshing = true) або черги запитів, тестуючи це в одній вкладці. У реальному світі користувачі тримають відкритими кілька вкладок: сторінку налаштувань, панель аналітики та кілька вікон з даними. Коли термін дії токена доступу закінчується, кожна вкладка незалежно виявляє помилку 401, кожна надсилає запит на оновлення, і бекенд — особливо якщо він застосовує ротацію токенів оновлення (refresh-token rotation) — сприймає другий запит як повтор і анулює всю сесію.

Ізоляція JavaScript створює проблему

Кожна вкладка браузера має власний контекст JavaScript. Змінні, таймери та прапорці в пам'яті невидимі для інших вкладок, навіть якщо вони мають однаковий origin. Прапорець, який каже «оновлення вже триває», існує лише всередині тієї вкладки, де він був встановлений. Інші вкладки не мають способу дізнатися, що токен оновлюється десь в іншому місці, тому всі вони запускають власні мережеві виклики. Проблема не в помилці в інтерцепторі; це фундаментальне обмеження ізоляції стану на стороні клієнта.

Web Locks API на допомогу

Коли вкладка утримує блокування (lock), будь-яка інша вкладка, яка запитує те саме блокування, повинна чекати, поки воно буде звільнене.

Як це працює для оновлення токена

  1. Виявлення 401 – Будь-яка вкладка, яка отримує відповідь «unauthorized», викликає navigator.locks.request('auth_token_refresh_lock', async lock => { … }).
  2. Отримання блокування – Якщо жодна інша вкладка не утримує блокування, поточна вкладка продовжує роботу; інакше вона стає на паузу, поки блокування не звільниться.
  3. Одне оновлення – Власник блокування надсилає запит на оновлення, зберігає новий токен доступу та мітку часу в localStorage, а потім автоматично звільняє блокування після завершення колбеку.
  4. Уникнення дублювання роботи – Коли вкладка, що чекала, нарешті отримує блокування, вона зчитує мітку часу з localStorage. Якщо токен було оновлено протягом визначеного вікна (наприклад, протягом останніх кількох секунд), вкладка пропускає мережевий виклик і оновлює свій токен у пам'яті з localStorage.
  5. Обробка збоїв – Якщо вкладка аварійно завершує роботу або закривається під час утримання блокування, браузер звільняє його, дозволяючи іншій вкладці повторити спробу оновлення.

Переваги одним поглядом

  • Нуль зайвих мережевих викликів – Тільки перша вкладка взаємодіє з бекендом.
  • Відсутність знищення сесії – Ротація токенів оновлення бачить лише одне використання, зберігаючи сесію активною.
  • Коректне відновлення – Звільнення блокування, кероване браузером, запобігає взаємним блокуванням (deadlocks), якщо вкладка зникає.

Контрольний список для впровадження

  • Огорніть логіку оновлення у ваш інтерцептор Axios (або fetch) за допомогою запиту блокування.
  • Зберігайте оновлений токен і мілісекундну мітку часу в localStorage (або в sessionStorage, якщо ви віддаєте перевагу даним лише для поточної сесії).
  • Коли блокування надано, порівняйте збережену мітку часу з Date.now(). Якщо різниця менша за ваш поріг, зчитайте токен зі сховища замість виклику бекенда.
  • Переконайтеся, що інтерцептор оновлює заголовки запитів токеном, отриманим зі сховища, перед повторним викликом оригінального API.
  • Протестуйте процес із кількома вкладками, симулюючи затримку мережі, щоб переконатися, що до сервера доходить лише один запит на оновлення.

Що може піти не так

На що звернути увагу далі

Висновок

Ставтеся до кожної вкладки браузера як до вузла в крихітній розподіленій системі. Використовуючи Web Locks API для серіалізації оновлень JWT, ви усуваєте дубльовані виклики, захищаєте ротацію токенів оновлення та підтримуєте активність користувачів у всіх їхніх відкритих вкладках.