Web Locks API می‌تواند از هجوم همزمان پنج تب باز به یک سرور احراز هویت با فراخوانی‌های refresh-token جلوگیری کند و کاربران را از خروج ناگهانی از حساب (logout) نجات دهد. با هماهنگ کردن فرآیند بازنشانی در میان تب‌ها، یک درخواست واحد جایگزین سیل درخواست‌هایی می‌شود که معمولاً هنگام استفاده از Refresh Token Rotation باعث از کار افتادن نشست (session) می‌گردد.

بار اضافی پنهان در یک نشست چندتبی

یک اپلیکیشن تک‌صفحه‌ای (SPA) معمولی، یک اینترسپتور Axios اضافه می‌کند که پاسخ‌های 401 را زیر نظر می‌گیرد، پرچم بولین isRefreshing را تغییر می‌دهد و درخواست‌های خروجی را تا رسیدن JWT جدید در صف نگه می‌دارد. اگر این جریان در یک تب تست شود، بی‌نقص عمل می‌کند.

اما اگر همان اپلیکیشن را در پنج تب باز کنید، اجازه دهید توکن دسترسی (access token) منقضی شود، هر پنج تب در همان میلی‌ثانیه متوجه خطای 401 می‌شوند. هر تب فکر می‌کند که باید توکن را بازنشانی کند، بنابراین پنج درخواست شناسایی‌شده برای refresh-token به سمت سرور احراز هویت مسابقه می‌گذارند. با استفاده از Refresh Token Rotation — یک اقدام امنیتی که به محض صدور توکن جدید، توکن بازنشانی قبلی را باطل می‌کند — سرور درخواست دوم را به عنوان یک حمله بازپخش (replay attack) شناسایی کرده، نشست را مشکوک تلقی کرده و آن را باطل می‌کند. در یک لحظه، کاربر از تمام تب‌ها خارج می‌شود.

علت اصلی، مدل ایزولاسیون (isolation model) جاوااسکریپت است. متغیری مانند isRefreshing فقط در تبی که آن را تنظیم کرده زندگی می‌کند؛ تب‌های دیگر راهی برای دانستن اینکه یک فرآیند بازنشانی در حال انجام است ندارند. نتیجه، یک مشکل کلاسیک همزمانی (concurrency) است، اما در اینجا «فرآیندها» به جای تردها (threads)، تب‌های مرورگر هستند.

چرا یک قفل بین‌تبی (cross-tab lock) ابزار مناسبی است

آنچه ما نیاز داریم روشی است تا تب‌ها درباره یک منبع مشترک — در این مورد، JWT تازه — با یکدیگر صحبت کنند. Web Locks API که از طریق navigator.locks در دسترس است، دقیقاً همین کار را انجام می‌دهد. این API به اسکریپت‌ها اجازه می‌دهد درخواستی برای یک قفل نام‌گذاری شده ارسال کنند که مرورگر آن را در تمام زمینه‌های (contexts) متعلق به یک مبدأ (origin) یکسان اعمال می‌کند. اگر قفلی از قبل در اختیار کسی باشد، سایر فراخوان‌ها در صف قرار می‌گیرند تا زمانی که دارنده قفل آن را آزاد کند یا مرورگر آن را لغو کند (مثلاً زمانی که تب کرش کند). بدون نیاز به سرور خارجی، بدون Polling، فقط هماهنگی بومی مرورگر.

پیاده‌سازی جریان بازنشانی مبتنی بر قفل

  1. تشخیص 401 – اینترسپتور Axios مانند قبل پاسخ غیرمجاز را دریافت می‌کند.
  2. درخواست قفل انحصاری – تب متد navigator.locks.request('auth_token_refresh_lock', async lock => { … }) را فراخوانی می‌کند. در هر لحظه فقط یک تب می‌تواند وارد تابع callback شود.
  3. بررسی مجدد قبل از اتصال به شبکه – داخل قفل، یک برچسب زمانی (timestamp) یا خودِ توکن را از localStorage بخوانید. اگر زمان سپری شده از این برچسب کمتر از چند ثانیه باشد، یعنی تب دیگری قبلاً توکن را بازنشانی کرده است؛ تب فعلی از فراخوانی شبکه صرف‌نظر کرده و به سادگی JWT جدید را از حافظه می‌خواند.
  4. بازنشانی در صورت نیاز – اگر برچسب زمانی ذخیره‌شده قدیمی باشد، درخواست بازنشانی را ارسال کنید، توکن جدید و زمان فعلی را در localStorage ذخیره کنید و سپس با بازگشت از تابع callback، قفل را آزاد کنید.
  5. ادامه درخواست‌های در صف – تمام تب‌های دیگری که منتظر بودند، قفل را یکی پس از دیگری به دست می‌آورند، برچسب زمانی جدید را می‌بینند و بدون ارسال درخواست مجدد، کار را تمام می‌کنند.
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، سرور تنها یک بار استفاده از توکن بازنشانی قدیمی را می‌بیند، بنابراین هرگز نشست را مشکوک تلقی نمی‌کند.
  • تاب‌آوری (Resilience) – اگر تبی که قفل را در اختیار دارد کرش کند، مرورگر به طور خودکار قفل را آزاد می‌کند و از ایجاد بن‌بست (deadlock) که می‌تواند تمام تب‌ها را متوقف کند، جلوگیری می‌کند.
  • مقیاس‌پذیری – کاربران می‌توانند ده‌ها تب باز کنند بدون اینکه خطر خروج زنجیره‌ای از حساب را داشته باشند، زیرا هماهنگی در داخل مرورگر باقی می‌ماند.

جنبه منفی: پشتیبانی مرورگر و جایگزین‌ها

Web Locks API یک ویژگی نسبتاً جدید است. مرورگرهای مدرن مبتنی بر Chromium و نسخه‌های اخیر Firefox از آن پشتیبانی می‌کنند، اما مرورگرهای قدیمی و Safari فاقد پشتیبانی بومی هستند. در محیط‌هایی که این API در دسترس نیست، توسعه‌دهندگان باید به تکنیک‌های کم‌اعتمادتری متوسل شوند — مانند پخش کردن یک رویداد سفارشی از طریق localStorage یا استفاده از یک Shared Worker — تا سیگنال‌دهی بین‌تبی را شبیه‌سازی کنند. این راهکارهای جایگزین فاقد محافظت خودکار در برابر بن‌بست (dead-lock) هستند که navigator.locks ارائه می‌دهد، بنابراین باید با احتیاط از آن‌ها استفاده کرد.

آنچه در ادامه باید دنبال کنید

  • Standardisation progress – Keep an eye on the API’s adoption curve; broader support will make the lock-based approach the default for any cross-tab coordination.
  • Library wrappers – A few open-source utilities are already abstracting the lock request pattern, making it easier to plug into existing Axios interceptors.
  • Security audits – While the lock solves the concurrency issue, the refresh endpoint must still enforce proper token rotation and rate limiting, as a single malicious tab could still flood the server with requests if the lock is bypassed.

The takeaway is simple: treat a set of open tabs as a distributed system and give them a native synchronization primitive. By wiring the Web Locks API into the token-refresh flow, developers eliminate the “browser tab token trap” and keep users logged in, no matter how many tabs they juggle.