Web Locks API ഉപയോഗിച്ച് അഞ്ച് ഓപ്പൺ ടാബുകൾ ഒരേസമയം ഒരു auth server-ലേക്ക് refresh-token കോളുകൾ അയക്കുന്നത് തടയാൻ സാധിക്കും, ഇത് ഉപയോക്താക്കൾ പെട്ടെന്ന് ലോഗ് ഔട്ട് ആകുന്നത് ഒഴിവാക്കുന്നു. ടാബുകൾക്കിടയിൽ റിഫ്രഷുകൾ ഏകോപിപ്പിക്കുന്നതിലൂടെ, Refresh Token Rotation ഉപയോഗിക്കുമ്പോൾ സാധാരണയായി സെഷൻ റദ്ദാക്കാൻ കാരണമാകുന്ന വലിയ തോതിലുള്ള റിക്വസ്റ്റുകൾക്ക് പകരം ഒരു സിംഗിൾ റിക്വസ്റ്റ് മാത്രം മതിയാകും.

ഒരു മൾട്ടി-ടാബ് സെഷനിലെ മറഞ്ഞിരിക്കുന്ന ഓവർലോഡ്

ഒരു സാധാരണ സിംഗിൾ-പേജ് ആപ്പ് (SPA), 401 റെസ്പോൺസിനായി കാത്തിരിക്കുന്ന ഒരു Axios interceptor ചേർക്കുന്നു; ഇത് ഒരു Boolean isRefreshing ഫ്ലാഗ് മാറ്റുകയും പുതിയൊരു JWT ലഭിക്കുന്നത് വരെ പുറത്തേക്ക് പോകുന്ന റിക്വസ്റ്റുകളെ ക്യൂ ചെയ്യുകയും ചെയ്യുന്നു. ഒരു ടാബിൽ ടെസ്റ്റ് ചെയ്യുമ്പോൾ ഈ പ്രക്രിയ കൃത്യമായി പ്രവർത്തിക്കുന്നു.

ഇതേ ആപ്പ് അഞ്ച് ടാബുകളിൽ തുറന്ന് access token കാലാവധി കഴിയാൻ അനുവദിച്ചാൽ, അഞ്ച് ടാബുകളും ഒരേ മില്ലിസെക്കൻഡിൽ 401 നോട്ടിസ് ചെയ്യും. ഓരോ ടാബും തനിയെ റിഫ്രഷ് ചെയ്യണമെന്ന് കരുതുന്നു, അതിനാൽ അഞ്ച് ഒരേപോലെയുള്ള refresh-token റിക്വസ്റ്റുകൾ auth server-ലേക്ക് എത്തുന്നു. Refresh Token Rotation—പുതിയൊരു ടോക്കൺ ലഭിച്ചാലുടൻ പഴയ റിഫ്രഷ് ടോക്കൺ അസാധുവാക്കുന്ന ഒരു സുരക്ഷാ സംവിധാനം—ഉപയോഗിക്കുമ്പോൾ, രണ്ടാമത്തെ റിക്വസ്റ്റിനെ ഒരു replay attack ആയി സെർവർ കണക്കാക്കുകയും സെഷൻ സുരക്ഷാ ഭീഷണിയിലാണെന്ന് രേഖപ്പെടുത്തി അത് റദ്ദാക്കുകയും ചെയ്യുന്നു. നിമിഷനേരം കൊണ്ട് ഉപയോക്താവ് എല്ലാ ടാബുകളിൽ നിന്നും ലോഗ് ഔട്ട് ചെയ്യപ്പെടുന്നു.

ഇതിന്റെ പ്രധാന കാരണം JavaScript-ന്റെ ഐസൊലേഷൻ മോഡലാണ് (isolation model). isRefreshing പോലുള്ള ഒരു വേരിയബിൾ അത് സെറ്റ് ചെയ്ത ടാബിൽ മാത്രമേ നിലനിൽക്കുകയുള്ളൂ; ഒരു റിഫ്രഷ് നിലവിൽ നടന്നുകൊണ്ടിരിക്കുകയാണെന്ന് അറിയാൻ മറ്റ് ടാബുകൾക്ക് കഴിയില്ല. ഇതിന്റെ ഫലം ഒരു ക്ലാസിക് കൺകറൻസി പ്രശ്നമാണ് (concurrency problem), എന്നാൽ ഇവിടെ "പ്രോസസ്സുകൾ" എന്നത് ത്രെഡുകൾക്ക് (threads) പകരം ബ്രൗസർ ടാബുകളാണ്.

എന്തുകൊണ്ട് ഒരു ക്രോസ്-ടാബ് ലോക്ക് (cross-tab lock) ആണ് ശരിയായ മാർഗ്ഗം?

ടാബുകൾക്കിടയിൽ ഒരു പങ്കിട്ട റിസോഴ്സിനെക്കുറിച്ച് (ഈ സാഹചര്യത്തിൽ പുതിയ JWT) പരസ്പരം സംസാരിക്കാനുള്ള ഒരു മാർഗ്ഗമാണ് നമുക്ക് വേണ്ടത്. navigator.locks ആയി ലഭ്യമാകുന്ന Web Locks API കൃത്യമായി അത് നൽകുന്നു. ഒരേ origin-ൽ ഉൾപ്പെടുന്ന എല്ലാ കോൺടെക്സ്റ്റുകളിലും ബ്രൗസർ നടപ്പിലാക്കുന്ന ഒരു പേര് നൽകപ്പെട്ട ലോക്ക് ആവശ്യപ്പെടാൻ ഇത് സ്ക്രിപ്റ്റുകളെ അനുവദിക്കുന്നു. ഒരു ലോക്ക് നിലവിൽ ഉപയോഗിച്ചുകൊണ്ടിരിക്കുകയാണെങ്കിൽ, അത് ഉപയോഗിക്കുന്നയാൾ വിട്ടുകൊടുക്കുന്നത് വരെയോ അല്ലെങ്കിൽ ബ്രൗസർ അത് റദ്ദാക്കുന്നത് വരെയോ (ഉദാഹരണത്തിന് ടാബ് ക്രാഷ് ആയാൽ) മറ്റ് റിക്വസ്റ്റുകൾ ക്യൂ ചെയ്യപ്പെടുന്നു. പുറമെ നിന്നുള്ള സെർവറുകളോ പോളിംഗോ (polling) ആവശ്യമില്ല, ബ്രൗസറിന്റെ സ്വാഭാവിക ഏകോപനം മാത്രം മതി.

ലോക്ക് അടിസ്ഥാനമാക്കിയുള്ള റിഫ്രഷ് പ്രക്രിയ നടപ്പിലാക്കുന്ന രീതി

  1. 401 കണ്ടെത്തുക – Axios interceptor മുമ്പത്തെപ്പോലെ തന്നെ അൺതോറൈസ്ഡ് (unauthorized) റെസ്പോൺസ് കണ്ടെത്തുന്നു.
  2. ഒരു എക്സ്ക്ലൂസീവ് ലോക്കിനായി ആവശ്യപ്പെടുക – ടാബ് navigator.locks.request('auth_token_refresh_lock', async lock => { … }) എന്ന് വിളിക്കുന്നു. ഒരു സമയം ഒരു ടാബിന് മാത്രമേ കോൾബാക്കിൽ (callback) പ്രവേശിക്കാൻ കഴിയൂ.
  3. നെറ്റ്‌വർക്കിലേക്ക് റിക്വസ്റ്റ് അയക്കുന്നതിന് മുമ്പ് വീണ്ടും പരിശോധിക്കുക – ലോക്കിനുള്ളിൽ, localStorage-ൽ നിന്ന് ഒരു ടൈംസ്റ്റാമ്പ് (അല്ലെങ്കിൽ ടോക്കൺ തന്നെ) വായിക്കുക. ടൈംസ്റ്റാമ്പ് ഏതാനും സെക്കൻഡുകൾ മാത്രം പഴക്കമുള്ളതാണെങ്കിൽ, മറ്റൊരു ടാബ് ഇതിനകം ടോക്കൺ റിഫ്രഷ് ചെയ്തിട്ടുണ്ടാകും; അങ്ങനെയെങ്കിൽ നിലവിലെ ടാബ് നെറ്റ്‌വർക്ക് കോൾ ഒഴിവാക്കി സ്റ്റോറേജ് నుండి പുതിയ JWT വായിക്കുന്നു.
  4. ആവശ്യമെങ്കിൽ റിഫ്രഷ് ചെയ്യുക – സ്റ്റോറേജ് ചെയ്ത ടൈംസ്റ്റാമ്പ് പഴയതാണെങ്കിൽ, റിഫ്രഷ് റിക്വസ്റ്റ് അയക്കുക, പുതിയ ടോക്കണും നിലവിലെ സമയവും localStorage-ൽ സൂക്ഷിക്കുക, തുടർന്ന് കോൾബാക്ക് റിട്ടേൺ ചെയ്തുകൊണ്ട് ലോക്ക് റിലീസ് ചെയ്യുക.
  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 ഒഴിവാക്കുന്നു.
  • സ്കെയിലബിലിറ്റി (Scalability) – ഏകോപനം ബ്രൗസറിനുള്ളിൽ തന്നെ നടക്കുന്നത് കൊണ്ട്, ലോഗ് ഔട്ട് ആകുമെന്ന ഭയമില്ലാതെ ഉപയോക്താക്കൾക്ക് ഡസൻ കണക്കിന് ടാബുകൾ തുറക്കാവുന്നതാണ്.

മറുവശം: ബ്രൗസർ സപ്പോർട്ടും ഫോളബാക്കുകളും

Web Locks API താരതമ്യേന പുതിയൊരു ഫീച്ചറാണ്. ആധുനിക Chromium അധിഷ്ഠിത ബ്രൗസറുകളും ഫയർഫോക്സിന്റെ (Firefox) പുതിയ പതിപ്പുകളും ഇത് പിന്തുണയ്ക്കുന്നുണ്ടെങ്കിലും, പഴയ ബ്രൗസറുകൾക്കും സഫാരിക്കും (Safari) ഇതിൽ നേറ്റീവ് സപ്പോർട്ട് ഇല്ല. ഈ API ലഭ്യമല്ലാത്ത സാഹചര്യങ്ങളിൽ, ടാബുകൾക്കിടയിൽ സിഗ്നലിംഗ് നൽകാനായി localStorage-ലൂടെ ഒരു കസ്റ്റം ഇവന്റ് ബ്രോഡ്കാസ്റ്റ് ചെയ്യുന്നതോ അല്ലെങ്കിൽ ഒരു shared worker ഉപയോഗിക്കുന്നതോ പോലുള്ള കുറഞ്ഞ വിശ്വാസ്യതയുള്ള രീതികൾ ഡെവലപ്പർമാർ ഉപയോഗിക്കേണ്ടി വരും. navigator.locks നൽകുന്ന ഓട്ടോമാറ്റിക് deadlock പ്രൊTEക്ഷൻ ഇത്തരം പരിഹാരങ്ങളിൽ ലഭിക്കില്ല, അതിനാൽ അവ ഉപയോഗിക്കുമ്പോൾ ജാഗ്രത പാലിക്കണം.

അടുത്തതായി ശ്രദ്ധിക്കേണ്ടവ

  • മാനദണ്ഡവൽക്കരണ പുരോഗതി – API-യുടെ ഉപയോഗം വർദ്ധിച്ചുവരുന്ന രീതി (adoption curve) ശ്രദ്ധിക്കുക; കൂടുതൽ പിന്തുണ ലഭിക്കുന്നത് ഏത് ക്രോസ്-ടാബ് ഏകോപനത്തിനും (cross-tab coordination) ലോക്ക് അധിഷ്ഠിത സമീപനത്തെ ഡിഫോൾട്ട് ആക്കും.
  • ലൈബ്രറി റാപ്പറുകൾ – ചില ഓപ്പൺ സോഴ്‌സ് യൂട്ടിലിറ്റികൾ ഇതിനകം തന്നെ ലോക്ക് റിക്വസ്റ്റ് പാറ്റേണിനെ ലഘൂകരിക്കുന്നുണ്ട്, ഇത് നിലവിലുള്ള Axios interceptors-ലേക്ക് എളുപ്പത്തിൽ ചേർക്കാൻ സഹായിക്കുന്നു.
  • സുരക്ഷാ ഓഡിറ്റുകൾ – ലോക്ക് കോൺകറൻസി (concurrency) പ്രശ്നം പരിഹരിക്കുന്നുണ്ടെങ്കിലും, റിഫ്രഷ് എൻഡ്‌പോയിന്റ് (refresh endpoint) കൃത്യമായ ടോക്കൺ റൊട്ടേഷനും റേറ്റ് ലിമിറ്റിംഗും നിർബന്ധമായും നടപ്പിലാക്കണം; കാരണം ലോക്ക് ബൈപാസ് ചെയ്യപ്പെട്ടാൽ, ഒരു ദുരുദ്ദേശ്യപരമായ ടാബ് സെർവറിലേക്ക് അമിതമായ റിക്വസ്റ്റുകൾ അയക്കാൻ സാധ്യതയുണ്ട്.

ഇതിന്റെ സാരം ലളിതമാണ്: തുറന്നിരിക്കുന്ന ടാബുകളെ ഒരു ഡിസ്ട്രിബ്യൂട്ടഡ് സിസ്റ്റമായി (distributed system) കണ്ട് അവയ്ക്ക് ഒരു നേറ്റീവ് സിൻക്രണൈസേഷൻ പ്രിമിറ്റീവ് (native synchronization primitive) നൽകുക. Web Locks API-യെ ടോക്കൺ-റിഫ്രഷ് ഫ്ലോയുമായി ബന്ധിപ്പിക്കുന്നതിലൂടെ, ഡെവലപ്പർമാർക്ക് “browser tab token trap” ഒഴിവാക്കാനും ഉപയോക്താക്കൾ എത്ര ടാബുകൾ ഉപയോഗിച്ചാലും അവരെ ലോഗിൻ ചെയ്ത അവസ്ഥയിൽ തന്നെ നിലനിർത്താനും സാധിക്കും.