Web Locks API може запобігти ситуації, коли п'ять відкритих вкладок одночасно «засипають» сервер автентифікації запитами на оновлення токена (refresh-token), рятуючи користувачів від раптового виходу з системи. Координуючи оновлення між вкладками, один запит замінює потік запитів, який зазвичай призводить до завершення сесії, якщо використовується Refresh Token Rotation.
Приховане перевантаження у сесії з багатьма вкладками
Типовий односторінковий додаток (SPA) додає Axios interceptor, який відстежує відповідь 401, змінює булевий прапор isRefreshing і ставить у чергу всі вихідні запити до моменту отримання нового JWT. При тестуванні в одній вкладці цей процес працює бездоганно.
Відкрийте той самий додаток у п'яти вкладках, дозвольте access token закінчитися, і всі п'ять вкладок помітять помилку 401 в одну і ту саму мілісекунду. Кожна вкладка вважатиме, що вона має оновити токен, тому п'ять ідентичних запитів на оновлення токена одночасно надійдуть на сервер автентифікації. Через Refresh Token Rotation — захід безпеки, який робить попередній refresh token недійсним одразу після видачі нового — сервер сприйме другий запит як replay attack, позначить сесію як скомпрометовану та анулює її. Користувача миттєво розлогінює в усіх вкладках.
Першопричиною є модель ізоляції JavaScript. Змінна на кшталт isRefreshing існує лише у тій вкладці, де вона була встановлена; інші вкладки не мають способу дізнатися, що процес оновлення вже триває. Результатом є класична проблема паралелізму (concurrency), але «процесами» тут є вкладки браузера, а не потоки.
Чому блокування між вкладками — це правильний інструмент
Нам потрібен спосіб, щоб вкладки могли «спілкуватися» між собою щодо спільного ресурсу — у цьому випадку свіжого JWT. Web Locks API, доступний через navigator.locks, надає саме це. Він дозволяє скриптам запитувати іменоване блокування, яке браузер забезпечує для всіх контекстів, що належать до одного походження (origin). Якщо блокування вже утримується, інші запити ставляться в чергу, доки власник не звільнить його або поки браузер не перерве його (наприклад, якщо вкладка аварійно завершує роботу). Жодних зовнішніх серверів, жодного опитування (polling) — лише нативна координація браузера.
Реалізація процесу оновлення на основі блокування
- Виявлення 401 – Axios interceptor перехоплює неавторизовану відповідь, як і раніше.
- Запит на ексклюзивне блокування – Вкладка викликає
navigator.locks.request('auth_token_refresh_lock', async lock => { … }). Лише одна вкладка може увійти в callback одночасно. - Подвійна перевірка перед мережевим запитом – Усередині блокування прочитайте мітку часу (або сам токен) із
localStorage. Якщо мітка часу була створена менше ніж кілька секунд тому, інша вкладка вже оновила токен; поточна вкладка пропускає мережевий запит і просто зчитує новий JWT зі сховища. - Оновлення за потреби – Якщо збережена мітка часу застаріла, надішліть запит на оновлення, збережіть новий токен і поточний час у
localStorage, а потім звільніть блокування, повернувши значення з callback. - Продовження черги запитів – Усі інші вкладки, що чекали, отримують блокування одна за одною, бачать свіжу мітку часу і завершують роботу без виконання нового запиту.
async function refreshIfNeeded() {
await navigator.locks.request('auth_token_refresh_lock', async lock => {
const lastRefresh = Number(localStorage.getItem('token_refreshed_at') || 0);
const now = Date.now();
if (now - lastRefresh < 5_000) return; // another tab already refreshed
const newToken = await callRefreshEndpoint(); // actual network call
localStorage.setItem('jwt', newToken);
localStorage.setItem('token_refreshed_at', now.toString());
});
}
Цей патерн гарантує, що незалежно від кількості відкритих вкладок, до сервера дійде лише один запит на оновлення.
Переваги, які можна виміряти
- Ефективність мережі – Один запит замінює п'ять, що значно зменшує використання пропускної здатності та навантаження на сервер.
- Безпека сесії – Завдяки Refresh Token Rotation сервер бачить лише одне використання старого refresh token, тому він ніколи не позначить сесію як скомпрометовану.
- Стійкість – Якщо вкладка, що утримує блокування, аварійно завершує роботу, браузер автоматично звільняє його, запобігаючи взаємному блокуванню (deadlock), яке в іншому випадку зупинило б усі вкладки.
- Масштабованість – Користувачі можуть відкривати десятки вкладок без ризику каскадного виходу з системи, оскільки координація відбувається всередині браузера.
Оборотний бік: підтримка браузерами та альтернативи
Web Locks API — це відносно нова функція. Сучасні браузери на базі Chromium та останні версії Firefox її підтримують, але старі браузери та Safari не мають нативної підтримки. У середовищах, де API недоступний, розробникам доводиться використовувати менш надійні методи — наприклад, трансляцію кастомної події через localStorage або використання Shared Worker — щоб імітувати передачу сигналів між вкладками. У таких обхідних шляхів немає автоматичного захисту від взаємного блокування, який забезпечує navigator.locks, тому їх слід використовувати з обережністю.
Що далі
- Прогрес стандартизації – Слідкуйте за кривою впровадження API; ширша підтримка зробить підхід на основі блокувань стандартом для будь-якої координації між вкладками.
- Обгортки бібліотек – Кілька утиліт із відкритим вихідним кодом уже абстрагують патерн запиту блокування, що полегшує їх інтеграцію в існуючі Axios interceptors.
- Аудит безпеки – Хоча блокування вирішує проблему паралелізму, ендпоінт оновлення (refresh endpoint) все одно має забезпечувати належну ротацію токенів та обмеження частоти запитів (rate limiting), оскільки одна шкідлива вкладка все ще може завалити сервер запитами, якщо блокування буде обійдено.
Висновок простий: ставтеся до набору відкритих вкладок як до розподіленої системи та надайте їм нативний примітив синхронізації. Інтегруючи Web Locks API у процес оновлення токенів, розробники позбуваються «пастки токенів у вкладках браузера» та забезпечують безперервну сесію користувачів, незалежно від того, скількома вкладками вони користуються одночасно.
