একটি টোকেন ট্র্যাপ একটি লাইভ সাইটে সমস্যা তৈরি করেছিল যখন একজন ব্যবহারকারীর খোলা পাঁচটি ট্যাব একই মিলিসেকেন্ডে একটি এক্সপায়ারড JWT রিফ্রেশ করার চেষ্টা করেছিল, যা ব্যাকএন্ডে ডুপ্লিকেট রিফ্রেশ রিকোয়েস্টের বন্যা বইয়ে দিয়েছিল এবং তাৎক্ষণিকভাবে সেশনটি ইনভ্যালিড করে দিয়েছিল। প্রতিটি ট্যাব ব্যবহারকারীকে লগ আউট করে দিয়েছিল, যা প্রমাণ করে যে টোকেন রিনিউয়ালের জন্য সিঙ্গেল-ট্যাব সমাধান আর যথেষ্ট নয়।

কেন টোকেন ট্র্যাপ গুরুত্বপূর্ণ

আধুনিক সিঙ্গেল-পেজ অ্যাপগুলো স্বল্পমেয়াদী access token এবং দীর্ঘমেয়াদী refresh token ব্যবহার করে। যখন access token এক্সপায়ার হয়ে যায়, ক্লায়েন্ট একটি রিফ্রেশ রিকোয়েস্ট পাঠায়, একটি নতুন টোকেন পেয়ার গ্রহণ করে এবং মূল কলটি পুনরায় চেষ্টা করে। বেশিরভাগ ডেভেলপার এই ফ্লো-কে একটি ইন-মেমরি ফ্ল্যাগ (যেমন, isRefreshing = true) বা রিকোয়েস্ট কিউ দিয়ে সুরক্ষিত করেন এবং এটি একটি সিঙ্গেল ট্যাবে পরীক্ষা করেন। বাস্তব ক্ষেত্রে ব্যবহারকারীরা একসাথে বেশ কয়েকটি ট্যাব খোলা রাখেন: একটি সেটিংস পেজ, একটি অ্যানালিটিক্স ড্যাশবোর্ড এবং কয়েকটি ডেটা ভিউ। যখন access token এক্সপায়ার হয়, প্রতিটি ট্যাব স্বাধীনভাবে 401 এরর শনাক্ত করে, প্রতিটি ট্যাব একটি রিফ্রেশ রিকোয়েস্ট পাঠায় এবং ব্যাকএন্ড—বিশেষ করে যখন এটি refresh-token rotation প্রয়োগ করে—দ্বিতীয় রিকোয়েস্টটিকে একটি 'রিপ্লে' হিসেবে গণ্য করে এবং পুরো সেশনটি বাতিল করে দেয়।

JavaScript আইসোলেশন সমস্যাটি তৈরি করে

প্রতিটি ব্রাউজার ট্যাব তার নিজস্ব JavaScript context-এ চলে। ভেরিয়েবল, টাইমার এবং ইন-মেমরি ফ্ল্যাগগুলো অন্য ট্যাবগুলোর কাছে অদৃশ্য থাকে, এমনকি তারা যদি একই অরিজিন শেয়ার করে তবুও। একটি ফ্ল্যাগ যা বলে “একটি রিফ্রেশ ইতিমধ্যে চলছে”, সেটি শুধুমাত্র সেই ট্যাবের ভেতরেই থাকে যা সেটি সেট করেছে। অন্য ট্যাবগুলোর জানার কোনো উপায় নেই যে টোকেনটি অন্য কোথাও রিফ্রেশ করা হচ্ছে, তাই তারা সবাই তাদের নিজস্ব নেটওয়ার্ক কল শুরু করে। সমস্যাটি interceptor-এর কোনো বাগ নয়; এটি ক্লায়েন্ট-সাইড স্টেট আইসোলেশনের একটি মৌলিক সীমাবদ্ধতা।

Web Locks API দিয়ে সমাধান

যখন একটি ট্যাব একটি লক ধরে রাখে, অন্য যেকোনো ট্যাব যদি একই লক চায় তবে সেটি রিলিজ না হওয়া পর্যন্ত অপেক্ষা করতে হবে।

টোকেন রিফ্রেশের ক্ষেত্রে এটি যেভাবে কাজ করে

  1. একটি 401 শনাক্ত করা – যে কোনো ট্যাব যা একটি unauthorized রেসপন্স পায়, সেটি navigator.locks.request('auth_token_refresh_lock', async lock => { … }) কল করে।
  2. লকটি অর্জন করা – যদি অন্য কোনো ট্যাব লকটি ধরে না রাখে, তবে বর্তমান ট্যাবটি এগিয়ে যায়; অন্যথায় এটি লকটি মুক্ত না হওয়া পর্যন্ত থেমে থাকে।
  3. একবার রিফ্রেশ করা – লক-হোল্ডার রিফ্রেশ রিকোয়েস্ট পাঠায়, নতুন access token এবং একটি টাইমস্ট্যাম্প localStorage-এ সংরক্ষণ করে এবং কলব্যাক শেষ হলে স্বয়ংক্রিয়ভাবে লকটি রিলিজ করে দেয়।
  4. ডুপ্লিকেট কাজ এড়িয়ে চলা – যখন একটি অপেক্ষমান ট্যাব অবশেষে লকটি পায়, তখন এটি localStorage থেকে টাইমস্ট্যাম্পটি পড়ে। যদি টোকেনটি একটি কনফিগারযোগ্য সময়ের মধ্যে (যেমন, গত কয়েক সেকেন্ডের মধ্যে) রিফ্রেশ করা হয়ে থাকে, তবে ট্যাবটি নেটওয়ার্ক কলটি স্কিপ করে এবং localStorage থেকে তার ইন-মেমরি টোকেন আপডেট করে নেয়।
  5. ক্র্যাশ হ্যান্ডেল করা – যদি লকটি ধরে থাকা অবস্থায় একটি ট্যাব ক্র্যাশ করে বা বন্ধ হয়ে যায়, তবে ব্রাউজার লকটি রিলিজ করে দেয়, যা অন্য একটি ট্যাবকে রিফ্রেশ করার চেষ্টা করার সুযোগ দেয়।

এক নজরে সুবিধাসমূহ

  • শূন্য অতিরিক্ত নেটওয়ার্ক কল – শুধুমাত্র প্রথম ট্যাবটি ব্যাকএন্ডের সাথে যোগাযোগ করে।
  • সেশন ধ্বংস হয় না – refresh-token rotation একটি মাত্র ব্যবহার দেখতে পায়, ফলে সেশনটি সচল থাকে।
  • সহজ রিকভারি – ব্রাউজার-পরিচালিত লক রিলিজ কোনো ট্যাব অদৃশ্য হয়ে গেলেও ডেডলক প্রতিরোধ করে।

ইমপ্লিমেন্টেশন চেকলিস্ট

  • আপনার Axios (বা fetch) interceptor-এ একটি লক রিকোয়েস্টের মাধ্যমে রিফ্রেশ লজিকটি র‍্যাপ (wrap) করুন।
  • রিফ্রেশ করা টোকেন এবং একটি মিলিসেকেন্ড টাইমস্ট্যাম্প localStorage-এ (অথবা আপনি যদি পার-সেশন ডেটা পছন্দ করেন তবে sessionStorage-এ) সংরক্ষণ করুন।
  • যখন একটি লক প্রদান করা হয়, তখন সংরক্ষিত টাইমস্ট্যাম্পটিকে Date.now()-এর সাথে তুলনা করুন। যদি পার্থক্য আপনার নির্ধারিত থ্রেশহোল্ডের নিচে হয়, তবে ব্যাকএন্ড কল করার পরিবর্তে স্টোরেজ থেকে টোকেনটি পড়ুন।
  • নিশ্চিত করুন যে interceptorটি মূল API কলটি পুনরায় করার আগে স্টোরেজ থেকে প্রাপ্ত টোকেন দিয়ে রিকোয়েস্ট হেডার আপডেট করে।
  • একাধিক ট্যাব দিয়ে ফ্লোটি পরীক্ষা করুন এবং নেটওয়ার্ক ল্যাটেন্সি সিমুলেট করে যাচাই করুন যে শুধুমাত্র একটি রিফ্রেশ রিকোয়েস্ট সার্ভারে পৌঁছাচ্ছে কি না।

কী কী ভুল হতে পারে

পরবর্তীতে কী খেয়াল রাখতে হবে

সারকথা

প্রতিটি ব্রাউজার ট্যাবকে একটি ক্ষুদ্র ডিস্ট্রিবিউটেড সিস্টেমের নোড হিসেবে বিবেচনা করুন। JWT রিফ্রেশগুলোকে সিরিয়ালাইজ করতে Web Locks API ব্যবহার করার মাধ্যমে, আপনি ডুপ্লিকেট কল দূর করতে পারেন, refresh-token rotation সুরক্ষিত করতে পারেন এবং ব্যবহারকারীদের তাদের সমস্ত খোলা ট্যাবে লগ-ইন অবস্থায় রাখতে পারেন।