Web Locks API ஐந்து திறந்திருக்கும் டேப்கள் (tabs) ஒரே நேரத்தில் auth server-க்கு refresh-token அழைப்புகளை அனுப்பிச் சுமையூட்டுவதைத் தடுக்க முடியும், இது பயனர்கள் திடீரென லாக்-அவுட் (logout) ஆவதிலிருந்து அவர்களைக் காக்கிறது. டேப்களுக்கு இடையே refresh செயல்பாடுகளை ஒருங்கிணைப்பதன் மூலம், Refresh Token Rotation பயன்படுத்தப்படும்போது வழக்கமாகத் தொடர் கோரிக்கைகளால் (flood) ஏற்படும் செஷன் ரத்து (session-kill) சிக்கலைத் தவிர்த்து, ஒரே ஒரு கோரிக்கையை மட்டும் அனுப்ப முடியும்.
பல டேப்கள் கொண்ட ஒரு செஷனில் (session) மறைந்திருக்கும் அதிகப்படியான சுமை
ஒரு சாதாரண single-page app, 401 பதிலைக் கண்காணிக்கும் ஒரு Axios interceptor-ஐக் கொண்டிருக்கும். அது ஒரு Boolean isRefreshing flag-ஐ மாற்றி, புதிய JWT வரும் வரை வெளியேறும் கோரிக்கைகளை வரிசைப்படுத்தும் (queue). ஒரு டேபில் சோதிக்கும்போது, இந்தச் செயல்முறை மிகச் சரியாகச் செயல்படும்.
அதே செயலியை ஐந்து டேப்களில் திறந்து, access token காலாவதியாகும் போது, ஐந்து டேப்களுமே ஒரே மில்லி விநாடியில் 401 பிழையைக் கவனிக்கும். ஒவ்வொரு டேப்பும் தான் refresh செய்ய வேண்டும் என்று நினைக்கும், இதனால் ஐந்து ஒரே மாதிரியான refresh-token கோரிக்கைகள் auth server-ஐ நோக்கிப் பாயும். Refresh Token Rotation—அதாவது ஒரு புதிய டோக்கன் வழங்கப்பட்டவுடன் பழைய refresh token-ஐ செல்லாததாக்கும் ஒரு பாதுகாப்பு முறை—பயன்படுத்தப்படும்போது, சர்வர் இரண்டாவது கோரிக்கையை ஒரு replay attack என்று கருதி, அந்த செஷனைப் பாதுகாப்பற்றது எனக் குறித்து ரத்து செய்துவிடும். இதனால் பயனர் அனைத்து டேப்களிலிருந்தும் உடனடியாக லாக்-அவுட் ஆகிவிடுவார்.
இதற்குக் காரணம் JavaScript-ன் isolation model ஆகும். isRefreshing போன்ற ஒரு மாறி (variable) அதை அமைத்த டேபில் மட்டுமே இருக்கும்; மற்ற டேப்களுக்கு refresh செயல்முறை ஏற்கனவே நடந்து கொண்டிருக்கிறது என்பதைத் தெரிந்துகொள்ள வழியில்லை. இது ஒரு பொதுவான concurrency சிக்கலாகும், ஆனால் இங்கு "processes" என்பது threads என்பதற்குப் பதிலாக பிரவுசர் டேப்கள் ஆகும்.
ஏன் ஒரு cross-tab lock சரியான கருவி?
டேப்கள் ஒரு பொதுவான வளத்தைப் (shared resource)—இந்தச் சூழலில் புதிய JWT-ஐப்—பற்றித் தங்களுக்குள் பேசிக்கொள்ள ஒரு வழி நமக்குத் தேவை. navigator.locks ஆக வெளிப்படுத்தப்படும் Web Locks API அதைச் சரியாக வழங்குகிறது. இது ஒரே origin-க்குச் சொந்தமான அனைத்து சூழல்களிலும் (contexts) பிரவுசர் நடைமுறைப்படுத்தும் ஒரு குறிப்பிட்ட lock-ஐ ஸ்கிரிப்ட்கள் கோர அனுமதிக்கிறது. ஒரு lock ஏற்கனவே பிடிபட்டிருந்தால், அதை வைத்திருப்பவர் விடுவிக்கும் வரை அல்லது பிரவுசர் அதை ரத்து செய்யும் வரை (உதாரணமாக, டேப் செயலிழக்கும் போது) மற்ற கோரிக்கைகள் வரிசைப்படுத்தப்படும். இதற்குத் தனிப்பட்ட சர்வர் அல்லது polling தேவையில்லை, பிரவுசரின் இயல்பான ஒருங்கிணைப்பே போதுமானது.
Lock-அடிப்படையிலான refresh flow-வை செயல்படுத்துதல்
- 401-ஐக் கண்டறிதல் – Axios interceptor முன்பைப் போலவே அங்கீகரிக்கப்படாத பதிலைப் பிடிக்கிறது.
- ஒரு exclusive lock-ஐக் கோருதல் – டேப்
navigator.locks.request('auth_token_refresh_lock', async lock => { … })என்று அழைக்கிறது. ஒரே நேரத்தில் ஒரு டேப் மட்டுமே callback-க்குள் நுழைய முடியும். - நெட்வொர்க்கைத் தொடர்பு கொள்வதற்கு முன் மீண்டும் சரிபார்த்தல் – Lock-க்குள் இருக்கும்போது,
localStorage-லிருந்து ஒரு timestamp-ஐ (அல்லது டோக்கனைத் தாமே) படிக்கவும். அந்த timestamp சில விநாடிகளுக்குக் குறைவாக இருந்தால், மற்றொரு டேப் ஏற்கனவே டோக்கனை refresh செய்துவிட்டது என்று அர்த்தம்; தற்போதைய டேப் நெட்வொர்க் அழைப்பைத் தவிர்த்துவிட்டு, சேமிப்பகத்திலிருந்து புதிய JWT-ஐ மட்டும் படிக்கிறது. - தேவைப்பட்டால் refresh செய்தல் – சேமிக்கப்பட்ட timestamp காலாவதியாகி இருந்தால், refresh கோரிக்கையை அனுப்பி, புதிய டோக்கனையும் தற்போதைய நேரத்தையும்
localStorage-இல் சேமித்துவிட்டு, callback-லிருந்து திரும்புவதன் மூலம் lock-ஐ விடுவிக்கவும். - வரிசைப்படுத்தப்பட்ட கோரிக்கைகளைத் தொடரச் செய்தல் – காத்திருந்த மற்ற அனைத்து டேப்களும் வரிசையாக lock-ஐப் பெற்று, புதிய 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 கோரிக்கை மட்டுமே சர்வருக்குச் சென்றடையும் என்பதை இந்த முறை உறுதி செய்கிறது.
நீங்கள் அளவிடக்கூடிய நன்மைகள்
- Network efficiency – ஐந்து கோரிக்கைகளுக்குப் பதிலாக ஒரே ஒரு கோரிக்கை மட்டுமே அனுப்பப்படுவதால், bandwidth மற்றும் சர்வர் சுமை பெருமளவு குறைகிறது.
- Session safety – Refresh Token Rotation மூலம், சர்வர் பழைய refresh token-ன் ஒரே ஒரு பயன்பாட்டைக் காண்பதால், செஷனைப் பாதுகாப்பற்றது என்று அது ஒருபோதும் கருதுவதில்லை.
- Resilience – Lock-ஐ வைத்திருக்கும் டேப் செயலிழந்தால், பிரவுசர் தானாகவே lock-ஐ விடுவிக்கிறது, இதனால் அனைத்து டேப்களையும் முடக்கும் deadlock தவிர்க்கப்படுகிறது.
- Scalability – ஒருங்கிணைப்பு பிரவுசருக்குள்ளேயே இருப்பதால், பயனர்கள் பல டேப்களைத் திறந்தாலும் லாக்-அவுட் ஆகும் அபாயம் இல்லை.
மறுபக்கம்: பிரவுசர் ஆதரவு மற்றும் மாற்று வழிகள் (fallbacks)
Web Locks API ஒரு ஒப்பீட்டளவில் புதிய அம்சம். நவீன Chromium சார்ந்த பிரவுசர்கள் மற்றும் சமீபத்திய Firefox பதிப்புகள் இதைச் செயல்படுத்துகின்றன, ஆனால் பழைய பிரவுசர்கள் மற்றும் Safari-இல் இதற்கு இயல்பான ஆதரவு இல்லை. இந்த API கிடைக்காத சூழல்களில், டெவலப்பர்கள் localStorage மூலம் ஒரு custom event-ஐப் பரப்புவது அல்லது shared worker பயன்படுத்துவது போன்ற குறைவான நம்பகமான நுட்பங்களைப் பயன்படுத்த வேண்டியிருக்கும். இத்தகைய மாற்று வழிகளில் navigator.locks வழங்கும் தானியங்கி deadlock பாதுகாப்பு இல்லை, எனவே அவற்றை எச்சரிக்கையுடன் பயன்படுத்த வேண்டும்.
அடுத்து எதைக் கவனிக்க வேண்டும்
- தரப்படுத்தல் முன்னேற்றம் – API-ன் பயன்பாட்டு வளர்ச்சியை (adoption curve) கவனிக்கவும்; பரவலான ஆதரவு கிடைத்தால், எந்தவொரு cross-tab ஒருங்கிணைப்பிற்கும் lock-அடிப்படையிலான அணுகுமுறை இயல்பானதாக (default) மாறும்.
- Library wrappers – சில open-source பயன்பாடுகள் ஏற்கனவே lock request முறையை எளிமைப்படுத்தி (abstracting) வருகின்றன, இது ஏற்கனவே உள்ள Axios interceptors-களில் இணைப்பதை எளிதாக்குகிறது.
- பாதுகாப்புத் தணிக்கைகள் – lock மூலம் concurrency சிக்கல் தீர்க்கப்பட்டாலும், refresh endpoint இன்னும் முறையான token rotation மற்றும் rate limiting ஆகியவற்றை உறுதி செய்ய வேண்டும்; ஏனெனில் lock தவிர்க்கப்பட்டால் (bypassed), ஒரு தீய நோக்கம் கொண்ட tab சர்வரில் அதிகப்படியான கோரிக்கைகளை (requests) அனுப்பக்கூடும்.
இதிலிருந்து நாம் கற்றுக்கொள்ள வேண்டியது எளிது: திறந்திருக்கும் பல tabs-களை ஒரு distributed system ஆகக் கருதி, அவற்றுக்கு ஒரு native synchronization primitive-ஐ வழங்க வேண்டும். Web Locks API-யை token-refresh flow-உடன் இணைப்பதன் மூலம், டெவலப்பர்கள் “browser tab token trap”-ஐத் தவிர்க்கலாம் மற்றும் பயனர்கள் எத்தனை tabs-களைப் பயன்படுத்தினாலும் அவர்களைத் தொடர்ந்து லாக்-இன் (logged in) நிலையில் வைத்திருக்க முடியும்.
