ה-Web Locks API יכול למנוע מחמישה טאבים פתוחים להציף בו-זמנית שרת אימות (auth server) בקריאות refresh-token, ובכך להציל משתמשים מניתוקים פתאומיים מהמערכת. באמצעות תיאום רעננים בין הטאבים, בקשה אחת בלבד מחליפה את ה"שיטפון" שבדרך כלל מפעיל סגירת סשן (session-kill) כאשר נעשה שימוש ב-Refresh Token Rotation.
העומס הנסתר בסשן מרובה-טאבים
אפליקציית Single-page טיפוסית מוסיפה Axios interceptor שעוקב אחר תגובת 401, משנה דגל Boolean בשם isRefreshing, ומכניס כל בקשה יוצאת לתור עד להגעת JWT חדש. בבדיקה בטאב בודד, התהליך עובד ללא תפקוד.
פתחו את אותה אפליקציה בחמישה טאבים, תנו ל-access token לפוג תוקף, וכל חמשת הטאבים יזהו את ה-401 באותו מילישנייה. כל טאב חושב שהוא חייב לבצע רענון, כך שחמש בקשות refresh-token זהות מתחרות על הגישה לשרת האימות. בשימוש ב-Refresh Token Rotation — אמצעי אבטחה שמבטל את ה-refresh token הקודם ברגע שמונפק חדש — השרת מזהה את הבקשה השנייה כמתקפת Replay, מסמן את הסשן כפרוץ ומבטל אותו. המשתמש מנותק מכל הטאבים ברגע אחד.
שורש הבעיה הוא מודל הבידוד של JavaScript. משתנה כמו isRefreshing חי רק בטאב שהגדיר אותו; לטאבים אחרים אין דרך לדעת שרענון כבר נמצא בעיצומו. התוצאה היא בעיית concurrency קלאסית, אך ה"תהליכים" הם טאבים של הדפדפן ולא threads.
למה נעילת cross-tab היא הכלי הנכון
מה שאנחנו צריכים זו דרך עבור טאבים לתקשר זה עם זה לגבי משאב משותף — במקרה זה, ה-JWT הטרי. ה-Web Locks API, הנגיש דרך navigator.locks, מספק בדיוק את זה. הוא מאפשר לסקריפטים לבקש נעילה בשם מסוים שהדפדפן אוכף על פני כל ההקשרים (contexts) השייכים לאותו origin. אם נעילה כבר מוחזקת, קוראים אחרים נכנסים לתור עד שהמחזיק משחרר אותה או שהדפדפן מבטל אותה (למשל, כשהטאב קורס). ללא שרת חיצוני, ללא polling, רק תיאום דפדפן מובנה (native).
מימוש תהליך הרענון מבוסס הנעילה
- זיהוי ה-401 – ה-Axios interceptor תופס את תגובת ה-unauthorized כפי שנעשה קודם.
- בקשת נעילה בלעדית (exclusive lock) – הטאב קורא ל-
navigator.locks.request('auth_token_refresh_lock', async lock => { … }). רק טאב אחד יכול להיכנס ל-callback בכל פעם. - בדיקה כפולה לפני פנייה לרשת – בתוך הנעילה, קרא timestamp (או את הטוקן עצמו) מ-
localStorage. אם ה-timestamp בן פחות מכמה שניות, טאב אחר כבר רענן את הטוקן; הטאב הנוכחי מדלג על קריאת הרשת ופשוט קורא את ה-JWT החדש מהאחסון. - רענון במידת הצורך – אם ה-timestamp השמור אינו עדכני, שלח את בקשת הרענון, שמור את הטוקן החדש ואת הזמן הנוכחי ב-
localStorage, ואז שחרר את הנעילה על ידי יציאה מה-callback. - המשך בקשות בתור – כל שאר הטאבים שחיכו מקבלים את הנעילה אחד אחרי השני, רואים את ה-timestamp המעודכן, ומסיימים מבלי לבצע בקשה נוספת.
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 הישן, ולכן לעולם לא יסמן את הסשן כפרוץ.
- חוסן (Resilience) – אם הטאב שמחזיק בנעילה קורס, הדפדפן משחרר את הנעילה באופן אוטומטי, מה שמונע deadlock שעלול לעצור את כל הטאבים.
- סקלאביליות (Scalability) – משתמשים יכולים לפתוח עשרות טאבים מבלי להסתכן בניתוק שרשרת (cascade logout), מכיוון שהתיאום נשאר בתוך הדפדפן.
הצד השני: תמיכת דפדפנים ופתרונות חלופיים (fallbacks)
ה-Web Locks API היא תכונה חדשה יחסית. דפדפנים מודרניים מבוססי Chromium וגרסאות אחרונות של Firefox תומכים בה, אך דפדפנים ישנים ו-Safari אינם כוללים תמיכה מובנית. בסביבות שבהן ה-API אינו זמין, מפתחים חייבים להשתמש בטכניקה פחות אמינה — כמו שידור אירוע מותאם אישית (custom event) דרך localStorage או שימוש ב-shared worker — כדי לדמות תקשורת cross-tab. לפתרונות עוקפים אלו חסרה ההגנה האוטומטית מפני deadlock ש-navigator.locks מספק, ולכן יש להשתמש בהם בזהירות.
מה כדאי לעקוב אחריו בהמשך
- התקדמות בתהליך הסטנדרטיזציה – עקבו אחר עקומת האימוץ של ה-API; תמיכה רחבה יותר תהפוך את הגישה מבוססת הנעילה (lock-based) לברירת המחדל עבור כל תיאום בין טאבים (cross-tab coordination).
- עטיפות ספרייה (Library wrappers) – מספר כלי עזר בקוד פתוח כבר מבצעים הפשטה (abstracting) של דפוס בקשת הנעילה, מה שמקל על חיבורם ל-Axios interceptors קיימים.
- ביקורות אבטחה – בעוד שהנעילה פותרת את בעיית ה-concurrency, נקודת הקצה של ה-refresh חייבת עדיין לאכוף רוטציה תקינה של טוקנים (token rotation) והגבלת קצב בקשות (rate limiting), שכן טאב זדוני בודד עדיין עלול להציף את השרת בבקשות אם הנעילה תעקוף.
המסקנה היא פשוטה: התייחסו לקבוצה של טאבים פתוחים כמערכת מבוזרת (distributed system) והעניקו להם פרימיטיב סנכרון (synchronization primitive) מובנה. על ידי שילוב ה-Web Locks API בתוך תהליך רענון הטוקן (token-refresh flow), מפתחים מסלקים את "מלכודת הטוקן של טאבי הדפדפן" ושומרים על המשתמשים מחוברים, ללא קשר למספר הטאבים שהם מנהלים.
