ٹکن ٹریپ (token trap) ایک لائیو سائٹ پر اس وقت پیش آیا جب ایک ہی صارف کے ذریعے کھولی گئی پانچ ٹیبز نے ایک ہی ملی سیکنڈ میں ایکسپائرڈ JWT کو ریفریش کرنے کی کوشش کی، جس سے بیک اینڈ (backend) پر ڈپلیکیٹ ریفریش ریکویسٹس کی بھرمار ہو گئی اور فوری طور پر سیشن منسوخ ہو گیا۔ ہر ٹیب نے صارف کو لاگ آؤٹ کر دیا، جس سے یہ ثابت ہوا کہ ٹکن کی تجدید کے لیے صرف ایک ٹیب والا حل اب کافی نہیں ہے۔
ٹکن ٹریپ کیوں اہم ہے
جدید سنگل پیج ایپس (single-page apps) مختصر مدت کے access tokens اور طویل مدت کے refresh token کا استعمال کرتی ہیں۔ جب access token ایکسپائر ہو جاتا ہے، تو کلائنٹ ایک ریفریش ریکویسٹ بھیجتا ہے، نیا ٹکن پیئر وصول کرتا ہے، اور اصل کال کو دوبارہ کرنے کی کوشش کرتا ہے۔ زیادہ تر ڈویلپرز اس فلو کو ان میموری فلیگ (مثلاً isRefreshing = true) یا ریکویسٹ کیو (request queue) کے ذریعے محفوظ کرتے ہیں، اور اسے صرف ایک ٹیب میں ٹیسٹ کرتے ہیں۔ حقیقی دنیا میں صارفین کئی ٹیبز کھولے رکھتے ہیں: ایک سیٹنگز پیج، ایک اینالیٹکس ڈیش بورڈ، اور ڈیٹا کے چند ویوز۔ جب access token ایکسپائر ہوتا ہے، تو ہر ٹیب آزادانہ طور پر 401 error کا پتہ لگاتا ہے، ہر ایک ریفریش ریکویسٹ بھیجتا ہے، اور بیک اینڈ—خاص طور پر جب وہ refresh-token rotation کو نافذ کرتا ہے—دوسری ریکویسٹ کو ایک ری پلے (replay) سمجھ کر پورا سیشن منسوخ کر دیتا ہے۔
JavaScript کا علیحدگی (isolation) مسئلہ پیدا کرتی ہے
ہر براؤزر ٹیب اپنا الگ JavaScript context چلاتا ہے۔ ویری ایبلز، ٹائمرز، اور ان میموری فلیگز دوسرے ٹیبز کے لیے نظر نہیں آتے، چاہے وہ ایک ہی اوریجن (origin) شیئر کرتے ہوں۔ ایک فلیگ جو کہتا ہے کہ "ریفریش کا عمل پہلے سے جاری ہے" صرف اسی ٹیب کے اندر رہتا ہے جس نے اسے سیٹ کیا ہو۔ دوسرے ٹیبز کے پاس یہ جاننے کا کوئی طریقہ نہیں ہوتا کہ ٹکن کہیں اور ریفریش ہو رہا ہے، اس لیے وہ سب اپنی اپنی نیٹ ورک کال شروع کر دیتے ہیں۔ مسئلہ انٹرسیپٹر (interceptor) میں کوئی بگ نہیں ہے؛ بلکہ یہ کلائنٹ سائیڈ اسٹیٹ آئسولیشن (client-side state isolation) کی ایک بنیادی حد ہے۔
Web Locks API: ایک حل
جب کوئی ٹیب لاک (lock) رکھتا ہے، تو کوئی بھی دوسرا ٹیب جو اسی لاک کے لیے درخواست کرتا ہے، اسے لاک کے ریلیز ہونے تک انتظار کرنا پڑتا ہے۔
ٹکن ریفریش کے لیے یہ کیسے کام کرتا ہے
- 401 کا پتہ لگانا – کوئی بھی ٹیب جسے غیر مجاز (unauthorized) رسپانس ملے، وہ
navigator.locks.request('auth_token_refresh_lock', async lock => { … })کو کال کرتا ہے۔ - لاک حاصل کرنا – اگر کوئی دوسرا ٹیب لاک نہیں رکھتا، تو موجودہ ٹیب آگے بڑھتا ہے؛ ورنہ وہ لاک کے آزاد ہونے تک رک جاتا ہے۔
- صرف ایک بار ریفریش کرنا – لاک رکھنے والا ٹیب ریفریش ریکویسٹ بھیجتا ہے، نیا access token اور ٹائم اسٹیمپ
localStorageمیں محفوظ کرتا ہے، اور پھر کال بیک ختم ہونے پر خود بخود لاک ریلیز کر دیتا ہے۔ - ڈپلیکیٹ کام سے بچنا – جب انتظار کرنے والا ٹیب آخر کار لاک حاصل کرتا ہے، تو وہ
localStorageسے ٹائم اسٹیمپ پڑھتا ہے۔ اگر ٹکن ایک مقررہ وقت کے اندر (مثلاً گزشتہ چند سیکنڈوں میں) ریفریش ہو چکا ہو، تو ٹیب نیٹ ورک کال کو چھوڑ دیتا ہے اورlocalStorageسے اپنا ان میموری ٹکن اپ ڈیٹ کر لیتا ہے۔ - کریشز کو سنبھالنا – اگر لاک رکھتے ہوئے کوئی ٹیب کریش ہو جائے یا بند ہو جائے، تو براؤزر لاک کو ریلیز کر دیتا ہے، جس سے دوسرے ٹیب کو ریفریش دوبارہ کرنے کی اجازت مل جاتی ہے۔
فوائد پر ایک نظر
- غیر ضروری نیٹ ورک کالز کا خاتمہ – صرف پہلا ٹیب بیک اینڈ سے بات کرتا ہے۔
- سیشن کا خاتمہ نہیں ہوگا – refresh-token rotation صرف ایک بار استعمال ہوتا ہے، جس سے سیشن برقرار رہتا ہے۔
- بہتر ریکوری – براؤزر کے ذریعے مینیج کیا جانے والا لاک ریلیز، ٹیب غائب ہونے کی صورت میں ڈیڈ لاکس (deadlocks) سے بچاتا ہے۔
عمل درآمد کے لیے چیک لسٹ
- اپنے Axios (یا fetch) انٹرسیپٹر میں لاک ریکویسٹ کے ساتھ ریفریش لاجک کو لپیٹیں۔
- ریفریش شدہ ٹکن اور ملی سیکنڈ ٹائم اسٹیمپ کو
localStorageمیں محفوظ کریں (یا اگر آپ فی سیشن ڈیٹا پسند کرتے ہیں توsessionStorageمیں)۔ - جب لاک مل جائے، تو محفوظ شدہ ٹائم اسٹیمپ کا
Date.now()سے موازنہ کریں۔ اگر فرق آپ کی حد (threshold) سے کم ہے، تو بیک اینڈ کو کال کرنے کے بجائے اسٹوریج سے ٹکن پڑھیں۔ - یقینی بنائیں کہ انٹرسیپٹر اصل API کال کو دوبارہ کرنے سے پہلے اسٹوریج سے حاصل کردہ ٹکن کے ساتھ ریکویسٹ ہیڈرز کو اپ ڈیٹ کرتا ہے۔
- نیٹ ورک لیٹنسی (latency) کی نقل کرتے ہوئے متعدد ٹیبز کے ساتھ اس فلو کا تجربہ کریں تاکہ تصدیق ہو سکے کہ صرف ایک ہی ریفریش ریکویسٹ سرور تک پہنچتی ہے۔
کیا غلط ہو سکتا ہے
آگے کیا دیکھنا ہے
حاصلِ کلام
ہر براؤزر ٹیب کو ایک چھوٹے سے ڈسٹریبیوٹڈ سسٹم (distributed system) کے نوڈ (node) کے طور پر سمجھیں۔ Web Locks API کا استعمال کرتے ہوئے JWT ریفریشز کو ترتیب دے کر، آپ ڈپلیکیٹ کالز کو ختم کرتے ہیں، refresh-token rotation کا تحفظ کرتے ہیں، اور صارفین کو ان کے تمام کھلے ٹیبز میں لاگ ان رکھتے ہیں۔
