टोकन ट्रैप (token trap) एक लाइव साइट पर तब हुआ जब एक ही यूजर द्वारा खोले गए पांच टैब ने एक ही मिलीसेकंड में एक्सपायर हो चुके JWT को रिफ्रेश करने की कोशिश की, जिससे बैकएंड पर डुप्लिकेट रिफ्रेश रिक्वेस्ट की बाढ़ आ गई और तुरंत सेशन अमान्य (invalidate) हो गया। हर टैब ने यूजर को लॉग आउट कर दिया, जिससे यह साबित हो गया कि टोकन रिन्यूअल के लिए सिंगल-टैब समाधान अब पर्याप्त नहीं है।
टोकन ट्रैप क्यों महत्वपूर्ण है
आधुनिक सिंगल-पेज ऐप्स (single-page apps) कम समय के लिए चलने वाले access tokens और लंबे समय तक चलने वाले refresh token का उपयोग करते हैं। जब access token एक्सपायर हो जाता है, तो क्लाइंट एक रिफ्रेश रिक्वेस्ट भेजता है, एक नया टोकन पेयर प्राप्त करता है, और मूल कॉल को फिर से प्रयास (retry) करता है। अधिकांश डेवलपर्स इस फ्लो को एक इन-मेमोरी फ्लैग (जैसे, isRefreshing = true) या रिक्वेस्ट क्यू (request queue) के साथ सुरक्षित करते हैं, और इसका परीक्षण एक सिंगल टैब में करते हैं। वास्तविक दुनिया में उपयोगकर्ता कई टैब खुले रखते हैं: एक सेटिंग्स पेज, एक एनालिटिक्स डैशबोर्ड, और कुछ डेटा व्यू। जब access token एक्सपायर होता है, तो प्रत्येक टैब स्वतंत्र रूप से 401 एरर का पता लगाता है, प्रत्येक रिफ्रेश रिक्वेस्ट भेजता है, और बैकएंड—विशेष रूप से जब यह refresh-token rotation लागू करता है—दूसरे अनुरोध को 'रीप्ले' (replay) मानता है और पूरे सेशन को रद्द कर देता है।
JavaScript आइसोलेशन समस्या पैदा करता है
प्रत्येक ब्राउज़र टैब अपना स्वयं का JavaScript कॉन्टेक्स्ट चलाता है। वेरिएबल्स, टाइमर और इन-मेमोरी फ्लैग अन्य टैब के लिए अदृश्य होते हैं, भले ही वे एक ही ओरिजिन (origin) साझा करते हों। एक फ्लैग जो कहता है कि "रिफ्रेश पहले से ही चल रहा है", केवल उसी टैब के अंदर रहता है जिसने इसे सेट किया है। अन्य टैबों को यह जानने का कोई तरीका नहीं है कि टोकन कहीं और रिफ्रेश किया जा रहा है, इसलिए वे सभी अपनी स्वयं की नेटवर्क कॉल शुरू कर देते हैं। समस्या इंटरसेप्टर (interceptor) में कोई बग नहीं है; यह क्लाइंट-साइड स्टेट आइसोलेशन (client-side state isolation) की एक मौलिक सीमा है।
Web Locks API: समस्या का समाधान
जब कोई टैब लॉक (lock) रखता है, तो उसी लॉक के लिए अनुरोध करने वाला कोई भी अन्य टैब तब तक प्रतीक्षा करता है जब तक कि उसे रिलीज़ न कर दिया जाए।
टोकन रिफ्रेश के लिए यह कैसे काम करता है
- 401 का पता लगाएं – कोई भी टैब जिसे अनधिकृत (unauthorized) रिस्पॉन्स मिलता है, वह
navigator.locks.request('auth_token_refresh_lock', async lock => { … })कॉल करता है। - लॉक प्राप्त करें – यदि कोई अन्य टैब लॉक नहीं रखता है, तो वर्तमान टैब आगे बढ़ता है; अन्यथा यह लॉक के मुक्त होने तक रुक जाता है।
- एक बार रिफ्रेश करें – लॉक-होल्डर रिफ्रेश रिक्वेस्ट भेजता है,
localStorageमें नया access token और एक टाइमस्टैम्प स्टोर करता है, और फिर कॉल-बैक समाप्त होने पर लॉक को स्वचालित रूप से रिलीज़ कर देता है। - डुप्लिकेट काम से बचें – जब प्रतीक्षा कर रहा टैब अंततः लॉक प्राप्त करता है, तो वह
localStorageसे टाइमस्टैम्प पढ़ता है। यदि टोकन एक कॉन्फ़िगर करने योग्य विंडो (जैसे, पिछले कुछ सेकंड) के भीतर रिफ्रेश किया गया था, तो टैब नेटवर्क कॉल को छोड़ देता है औरlocalStorageसे अपना इन-मेमोरी टोकन अपडेट कर लेता है। - क्रैश को संभालें – यदि लॉक पकड़े हुए कोई टैब क्रैश हो जाता है या बंद हो जाता है, तो ब्राउज़र लॉक को रिलीज़ कर देता है, जिससे दूसरे टैब को रिफ्रेश का पुनः प्रयास करने की अनुमति मिलती है।
एक नज़र में लाभ
- शून्य अनावश्यक नेटवर्क कॉल – केवल पहला टैब बैकएंड से बात करता है।
- सेशन नष्ट नहीं होता – refresh-token rotation केवल एक बार उपयोग देखता है, जिससे सेशन जीवित रहता है।
- सुचारू रिकवरी – ब्राउज़र-प्रबंधित लॉक रिलीज़, यदि कोई टैब गायब हो जाता है तो डेडलॉक (deadlock) को रोकता है।
इम्प्लीमेंटेशन चेकलिस्ट
- अपने Axios (या fetch) इंटरसेप्टर में लॉक रिक्वेस्ट के साथ रिफ्रेश लॉजिक को लपेटें (wrap करें)।
- रिफ्रेश किए गए टोकन और मिलीसेकंड टाइमस्टैम्प को
localStorageमें स्टोर करें (या यदि आप प्रति-सेशन डेटा पसंद करते हैं तोsessionStorageमें)। - जब लॉक दिया जाता है, तो स्टोर किए गए टाइमस्टैम्प की तुलना
Date.now()से करें। यदि अंतर आपके थ्रेशोल्ड (threshold) से कम है, तो बैकएंड को कॉल करने के बजाय स्टोरेज से टोकन पढ़ें। - सुनिश्चित करें कि इंटरसेप्टर मूल API कॉल को दोबारा करने से पहले स्टोरेज से प्राप्त टोकन के साथ रिक्वेस्ट हेडर को अपडेट करता है।
- नेटवर्क लेटेंसी (latency) का अनुकरण करते हुए कई टैब के साथ इस फ्लो का परीक्षण करें ताकि यह सत्यापित किया जा सके कि केवल एक ही रिफ्रेश रिक्वेस्ट सर्वर तक पहुँचती है।
क्या गलत हो सकता है
आगे क्या ध्यान रखें
निष्कर्ष
प्रत्येक ब्राउज़र टैब को एक छोटे वितरित सिस्टम (distributed system) के नोड के रूप में मानें। Web Locks API का उपयोग करके JWT रिफ्रेश को सीरियलाइज़ (serialize) करके, आप डुप्लिकेट कॉल को समाप्त करते हैं, refresh-token rotation की सुरक्षा करते हैं, और उपयोगकर्ताओं को उनके सभी खुले टैब में लॉग इन रखते हैं।
