Web Locks API পাঁচটি খোলা ট্যাবকে একই সাথে অথ সার্ভারে (auth server) রিফ্রেশ-টোকেন কল পাঠিয়ে অতিরিক্ত চাপ সৃষ্টি করা থেকে আটকাতে পারে, যা ব্যবহারকারীদের হঠাৎ লগআউট হওয়া থেকে রক্ষা করে। ট্যাবগুলোর মধ্যে রিফ্রেশ প্রক্রিয়া সমন্বয় করার মাধ্যমে, একটি মাত্র রিকোয়েস্ট সেই ভিড়কে প্রতিস্থাপন করে যা সাধারণত Refresh Token Rotation ব্যবহারের সময় সেশন-কিল (session-kill) ট্রিগার করে।
একটি মাল্টি-ট্যাব সেশনে লুকিয়ে থাকা অতিরিক্ত চাপ
একটি সাধারণ সিঙ্গেল-পেজ অ্যাপ (single-page app) একটি Axios interceptor যোগ করে যা 401 রেসপন্সের জন্য নজর রাখে, একটি Boolean isRefreshing ফ্ল্যাগ পরিবর্তন করে এবং একটি নতুন JWT না আসা পর্যন্ত সমস্ত আউটগোয়িং রিকোয়েস্ট কিউ (queue) করে রাখে। একটি ট্যাবে পরীক্ষা করলে এই প্রক্রিয়াটি নিখুঁতভাবে কাজ করে।
একই অ্যাপ পাঁচটি ট্যাবে খুলুন, অ্যাক্সেস টোকেনটি এক্সপায়ার হতে দিন, এবং দেখবেন পাঁচটি ট্যাবই একই মিলিসেকেন্ডে 401 রেসপন্সটি লক্ষ্য করেছে। প্রতিটি ট্যাব মনে করে যে তাকে রিফ্রেশ করতে হবে, ফলে পাঁচটি একই ধরণের রিফ্রেশ-টোকেন রিকোয়েস্ট অথ সার্ভারের দিকে ধাবিত হয়। Refresh Token Rotation—একটি নিরাপত্তা ব্যবস্থা যা নতুন টোকেন ইস্যু করার সাথে সাথে পূর্ববর্তী রিফ্রেশ টোকেনটিকে ইনভ্যালিড করে দেয়—এর ফলে সার্ভার দ্বিতীয় রিকোয়েস্টটিকে একটি replay attack হিসেবে গণ্য করে, সেশনটিকে আপসকৃত (compromised) হিসেবে চিহ্নিত করে এবং তা বাতিল করে দেয়। মুহূর্তের মধ্যেই ব্যবহারকারী প্রতিটি ট্যাব থেকে লগআউট হয়ে যান।
এর মূল কারণ হলো জাভাস্ক্রিপ্টের আইসোলেশন মডেল (isolation model)। isRefreshing-এর মতো একটি ভেরিয়েবল শুধুমাত্র সেই ট্যাবে থাকে যা এটি সেট করেছে; অন্য ট্যাবগুলোর জানার কোনো উপায় নেই যে একটি রিফ্রেশ প্রক্রিয়া ইতিমধ্যে চলছে। এর ফলাফল হলো একটি ক্লাসিক কনকারেন্সি সমস্যা (concurrency problem), তবে এখানে "প্রসেস" হলো থ্রেডের পরিবর্তে ব্রাউজার ট্যাব।
কেন একটি ক্রস-ট্যাব লক সঠিক সমাধান
আমাদের যা প্রয়োজন তা হলো ট্যাবগুলোর মধ্যে একটি শেয়ারড রিসোর্স—এই ক্ষেত্রে নতুন JWT—সম্পর্কে কথা বলার একটি উপায়। Web Locks API, যা navigator.locks হিসেবে পরিচিত, ঠিক সেটিই প্রদান করে। এটি স্ক্রিপ্টগুলোকে একটি নির্দিষ্ট নামের লক (named lock) অনুরোধ করতে দেয় যা ব্রাউজার একই অরিজিনভুক্ত (same origin) সমস্ত কনটেক্সটের মধ্যে কার্যকর করে। যদি একটি লক ইতিমধ্যে কেউ ধরে থাকে, তবে অন্য কলাররা (callers) ততক্ষণ পর্যন্ত কিউতে অপেক্ষা করবে যতক্ষণ না লক হোল্ডার এটি রিলিজ করে অথবা ব্রাউজার এটি বাতিল করে (উদাহরণস্বরূপ, যখন ট্যাবটি ক্র্যাশ করে)। কোনো এক্সটার্নাল সার্ভার নেই, কোনো পোলিং (polling) নেই, শুধু নেটিভ ব্রাউজার কোঅর্ডিনেশন।
লক-ভিত্তিক রিফ্রেশ ফ্লো ইমপ্লিমেন্ট করা
- 401 শনাক্ত করা – Axios interceptor আগের মতোই আনঅথরাইজড রেসপন্সটি ধরে ফেলে।
- একটি এক্সক্লুসিভ লক চাওয়া – ট্যাবটি
navigator.locks.request('auth_token_refresh_lock', async lock => { … })কল করে। এক সময়ে কেবল একটি ট্যাবই কলব্যাক ফাংশনে প্রবেশ করতে পারে। - নেটওয়ার্কে রিকোয়েস্ট পাঠানোর আগে পুনরায় যাচাই করা – লকের ভেতরে,
localStorageথেকে একটি টাইমস্ট্যাম্প (অথবা টোকেনটি নিজেই) পড়ুন। যদি টাইমস্ট্যাম্পটি কয়েক সেকেন্ডের কম পুরনো হয়, তবে বুঝতে হবে অন্য একটি ট্যাব ইতিমধ্যে টোকেনটি রিফ্রেশ করেছে; বর্তমান ট্যাবটি নেটওয়ার্ক কল এড়িয়ে যাবে এবং সরাসরি স্টোরেজ থেকে নতুন JWT পড়ে নেবে। - প্রয়োজন হলে রিফ্রেশ করা – যদি সংরক্ষিত টাইমস্ট্যাম্পটি পুরনো হয়, তবে রিফ্রেশ রিকোয়েস্ট পাঠান, নতুন টোকেন এবং বর্তমান সময়
localStorage-এ সংরক্ষণ করুন, তারপর কলব্যাক থেকে রিটার্ন করে লকটি রিলিজ করুন। - কিউতে থাকা রিকোয়েস্টগুলো পুনরায় শুরু করা – অপেক্ষমান অন্যান্য সমস্ত ট্যাব একে একে লকটি অর্জন করে, নতুন টাইমস্ট্যাম্পটি দেখে এবং কোনো নতুন রিকোয়েস্ট না করেই কাজ শেষ করে।
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) হওয়া প্রতিরোধ করে এবং অন্য ট্যাবগুলোকে আটকে পড়া থেকে বাঁচায়।
- স্কেলেবিলিটি – ব্যবহারকারীরা কোনো ক্যাস্কেড লগআউটের ঝুঁকি ছাড়াই ডজন ডজন ট্যাব খুলতে পারেন, কারণ কোঅর্ডিনেশন ব্রাউজারের মধ্যেই সীমাবদ্ধ থাকে।
অন্য দিক: ব্রাউজার সাপোর্ট এবং ফলব্যাক (fallbacks)
Web Locks API একটি তুলনামূলক নতুন ফিচার। আধুনিক Chromium-ভিত্তিক ব্রাউজার এবং Firefox-এর সাম্প্রতিক সংস্করণগুলো এটি সাপোর্ট করে, কিন্তু পুরনো ব্রাউজার এবং Safari-তে এর নেটিভ সাপোর্ট নেই। যেসব পরিবেশে এই API পাওয়া যায় না, সেখানে ডেভেলপারদের কম নির্ভরযোগ্য কৌশল ব্যবহার করতে হয়—যেমন localStorage-এর মাধ্যমে একটি কাস্টম ইভেন্ট ব্রডকাস্ট করা বা shared worker ব্যবহার করা—যাতে ক্রস-ট্যাব সিগন্যালিংয়ের কাছাকাছি পৌঁছানো যায়। এই বিকল্প পদ্ধতিগুলোতে navigator.locks-এর মতো স্বয়ংক্রিয় ডেডলক সুরক্ষা নেই, তাই এগুলো সতর্কতার সাথে ব্যবহার করা উচিত।
পরবর্তীতে যা খেয়াল রাখতে হবে
- স্ট্যান্ডার্ডাইজেশনের অগ্রগতি – API-এর গ্রহণের হারের (adoption curve) দিকে নজর রাখুন; ব্যাপক সমর্থন থাকলে যেকোনো ক্রস-ট্যাব কোঅর্ডিনেশনের জন্য লক-ভিত্তিক পদ্ধতিটি ডিফল্ট হিসেবে বিবেচিত হবে।
- লাইব্রেরি র্যাপার – কিছু ওপেন-সোর্স ইউটিলিটি ইতিমধ্যেই লক রিকোয়েস্ট প্যাটার্নকে অ্যাবস্ট্রাক্ট করছে, যা বিদ্যমান Axios interceptor-গুলোর সাথে যুক্ত করা সহজ করে তুলছে।
- সিকিউরিটি অডিট – যদিও লকটি কনকারেন্সি (concurrency) সমস্যার সমাধান করে, তবুও রিফ্রেশ এন্ডপয়েন্টটিকে অবশ্যই সঠিক টোকেন রোটেশন এবং রেট লিমিটিং নিশ্চিত করতে হবে, কারণ লকটি বাইপাস করা সম্ভব হলে একটি ক্ষতিকারক ট্যাব সার্ভারে রিকোয়েস্টের বন্যা বইয়ে দিতে পারে।
মূল কথাটি সহজ: খোলা ট্যাবগুলোর একটি সেটকে একটি ডিস্ট্রিবিউটেড সিস্টেম হিসেবে বিবেচনা করুন এবং তাদের একটি নেটিভ সিনক্রোনাইজেশন প্রিমিটিভ (native synchronization primitive) প্রদান করুন। টোকেন-রিফ্রেশ ফ্লো-তে Web Locks API যুক্ত করার মাধ্যমে, ডেভেলপাররা “browser tab token trap” দূর করতে পারেন এবং ব্যবহারকারী যতগুলো ট্যাবই ব্যবহার করুন না কেন, তাদের লগ-ইন অবস্থায় রাখতে পারেন।
