ToolDeck

JWT Decoder JavaScript — atob(), TextDecoder ও jose

·JavaScript Performance Engineer·পর্যালোচনা করেছেনSophie Laurent·প্রকাশিত

বিনামূল্যে অনলাইন JWT ডিকোডার সরাসরি আপনার ব্রাউজারে ব্যবহার করুন — ইনস্টলের প্রয়োজন নেই।

JWT ডিকোডার অনলাইনে ব্যবহার করুন →

আমি যতগুলি authentication flow তৈরি করেছি, প্রতিটি শেষ পর্যন্ত একই জায়গায় পৌঁছায়: একটি JWT কুকি, হেডার বা OAuth callback URL-এ বসে আছে, এবং আপনাকে ভেতরে কী আছে তা পড়তে হবে। JavaScript-এ একটি JWT decoder কোনো npm প্যাকেজের প্রয়োজন রাখে না। টোকেনের header এবং payload শুধুমাত্র Base64url-এনকোড করা JSON, এবং ব্রাউজার ও Node.js উভয়ই এগুলি ডিকোড করার জন্য প্রয়োজনীয় সবকিছু নিয়ে আসে। এই গাইডটি JWT-এর জন্য সম্পূর্ণ JavaScript text decoder পাইপলাইন কভার করে: টোকেন বিভক্ত করা, নর্মালাইজ করা base64url থেকে স্ট্যান্ডার্ড Base64, atob() এবং TextDecoder সহ সঠিক UTF-8 হ্যান্ডলিং, Node.js Buffer.from(), jose দিয়ে সিগনেচার যাচাই, এবং ডেভেলপাররা প্রতিদিন যেসব সাধারণ ভুল করেন। দ্রুত এককালীন পরীক্ষার জন্য, পরিবর্তে অনলাইন JWT Decoder ব্যবহার করুন। সমস্ত উদাহরণ ES2020+ এবং Node.js 18+ টার্গেট করে।

  • JWT-কে "." দিয়ে বিভক্ত করুন — index 0 হলো header, index 1 হলো payload, index 2 হলো signature।
  • atob() Base64 ডিকোড করে কিন্তু Latin-1 রিটার্ন করে, UTF-8 নয়। non-ASCII claims-এর জন্য TextDecoder বা Buffer.from() ব্যবহার করুন।
  • Buffer.from(segment, "base64url") Node.js-এ base64url সরাসরি হ্যান্ডেল করে — কোনো ম্যানুয়াল অক্ষর প্রতিস্থাপনের প্রয়োজন নেই।
  • ডিকোডিং যাচাইকরণ নয়। সার্ভার-সাইডে সিগনেচার না পরীক্ষা করে ডিকোড করা JWT থেকে claims কখনো বিশ্বাস করবেন না।
  • jose লাইব্রেরি উভয়ই করে: এটি HS256/RS256/ES256 সিগনেচার যাচাই করে এবং একটি কলেই ডিকোড করা payload রিটার্ন করে।

JWT ডিকোডিং কী?

একটি JSON Web Token হলো ডট দিয়ে আলাদা করা তিনটি Base64url-এনকোড করা সেগমেন্ট। প্রথম সেগমেন্টটি header, দ্বিতীয়টি payload (আপনার আসলে দরকারি claims), এবং তৃতীয়টি ক্রিপ্টোগ্রাফিক signature। header হলো একটি ছোট JSON অবজেক্ট যা টোকেনটি নিজেকে বর্ণনা করে। এর সবচেয়ে গুরুত্বপূর্ণ ক্ষেত্র হলো alg — সাইনিং অ্যালগরিদম (যেমন, HS256, RS256, ES256)।typ ক্ষেত্রটি প্রায় সবসময় "JWT", এবং ঐচ্ছিক kid ক্ষেত্রটি চিহ্নিত করে কোন কী টোকেনটি সাইন করতে ব্যবহৃত হয়েছিল — একটি identity provider কী রোটেট করলে এবং একাধিক পাবলিক কী সহ JWKS endpoint প্রকাশ করলে এটি গুরুত্বপূর্ণ।

payload claims বহন করে। RFC 7519 সাতটি নিবন্ধিত claim নাম সংজ্ঞায়িত করে: sub (subject — সাধারণত user ID), iss (issuer — auth সার্ভার URL), aud (audience — টোকেন যে API-এর জন্য উদ্দিষ্ট), iat (ইস্যু-করা টাইমস্ট্যাম্প), exp (মেয়াদ শেষ টাইমস্ট্যাম্প), nbf (not-before টাইমস্ট্যাম্প), এবং jti (JWT ID — replay attack প্রতিরোধে ব্যবহৃত)। সমস্ত টাইমস্ট্যাম্প Unix epoch সেকেন্ডে, মিলিসেকেন্ডে নয়। signature সেগমেন্ট হলো কাঁচা বাইনারি — একটি keyed HMAC digest বা asymmetric digital signature। এটি অন্য সেগমেন্টের মতো Base64url-এনকোড করা, কিন্তু এর বাইটগুলি JSON নয় এবং মানুষের পাঠযোগ্য কোনো কাঠামো নেই।

ব্যবহারিকভাবে, আপনি তিনটি সাধারণ কারণে JavaScript-এ JWT ডিকোড করেন। প্রথমত, ডিবাগিং: আপনার কাছে OAuth flow বা পরীক্ষার পরিবেশ থেকে একটি টোকেন আছে এবং আপনি নিশ্চিত করতে চান claims auth সার্ভার যা ইস্যু করার কথা তার সাথে মেলে। দ্বিতীয়ত, একটি অতিরিক্ত API কল ছাড়াই ক্লায়েন্ট সাইডে প্রদর্শনের জন্য token payload থেকে লগইন করা ব্যবহারকারীর নাম, avatar URL, বা role badge পড়া। তৃতীয়ত, refresh চেষ্টা করার আগে মেয়াদ শেষের সময় পরীক্ষা: যদি exp পরবর্তী 60 সেকেন্ডের মধ্যে থাকে, পরবর্তী API কলের আগে একটি silent refresh ট্রিগার করুন বরং 401 response-এর জন্য অপেক্ষা করার চেয়ে।

ডিকোডিং টোকেনটি বৈধ বা টেম্পার করা হয়েছে কিনা তা পরীক্ষা করে না। এটি verification নামক একটি পৃথক অপারেশন, যার জন্য HMAC secret বা RSA/ECDSA পাবলিক কী প্রয়োজন। যে কেউ JWT ডিকোড করতে পারে। শুধুমাত্র সঠিক কী-এর ধারক একটি যাচাই করতে পারে। এই পার্থক্যটি অনেক ডেভেলপারকে বিভ্রান্ত করে, বিশেষত ক্লায়েন্ট-সাইড auth flow তৈরি করার সময় যেখানে ডিকোড করা claims প্রদর্শন করা হয় কিন্তু একটি verified backend check ছাড়া authorization সিদ্ধান্তের জন্য কখনো বিশ্বাস করা উচিত নয়।

Before · json
After · json
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcxMTYxMDAwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
// Header
{ "alg": "HS256" }

// Payload
{
  "sub": "usr_921f",
  "role": "admin",
  "iat": 1711610000
}

atob() + TextDecoder — ব্রাউজার-নেটিভ JWT ডিকোড

JWT ডিকোড করার জন্য ব্রাউজার-নেটিভ পাইপলাইনে চারটি ধাপ আছে। প্রথমত, তিনটি সেগমেন্ট পেতে টোকেন স্ট্রিংকে "." দিয়ে বিভক্ত করুন। দ্বিতীয়ত, - কে + দিয়ে এবং _ কে / দিয়ে প্রতিস্থাপন করে base64url সেগমেন্ট নর্মালাইজ করুন, তারপর দৈর্ঘ্য 4-এর গুণিতক না হওয়া পর্যন্ত = অক্ষর দিয়ে প্যাড করুন। তৃতীয়ত, Base64 কে বাইনারি স্ট্রিংয়ে ডিকোড করতে atob() কল করুন। চতুর্থত, TextDecoder ব্যবহার করে বাইনারি স্ট্রিংটি সঠিক UTF-8-এ রূপান্তর করুন। শেষ ধাপটি গুরুত্বপূর্ণ কারণ atob() Latin-1 রিটার্ন করে। মাল্টি-বাইট অক্ষর — ইমোজি, CJK টেক্সট, Latin-1 রেঞ্জের বাইরে উচ্চারণ চিহ্ন সহ অক্ষর — JavaScript text decoder ধাপ ছাড়া বিকৃত হয়ে যায়।

JavaScript — minimal JWT decode
const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcxMTYxMDAwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";

function decodeJwtPayload(jwt) {
  const base64Url = jwt.split(".")[1];
  const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
  const padded = base64.padEnd(base64.length + (4 - (base64.length % 4)) % 4, "=");
  const binary = atob(padded);
  const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0));
  const json = new TextDecoder("utf-8").decode(bytes);
  return JSON.parse(json);
}

console.log(decodeJwtPayload(token));
// { sub: "usr_921f", role: "admin", iat: 1711610000 }

প্যাডিং ধাপটি সহজে উপেক্ষিত হয়। JWT তার Base64url সেগমেন্ট থেকে trailing = অক্ষরগুলি সরিয়ে দেয় কারণ JWT স্পেসিফিকেশন (RFC 7515) padding ছাড়া base64url সংজ্ঞায়িত করে। কিন্তু কিছু ব্রাউজার ইঞ্জিনে atob() একটি InvalidCharacterError থ্রো করে যদি ইনপুট দৈর্ঘ্য 4 দিয়ে বিভাজ্য না হয়। padEnd() দিয়ে সতর্কতামূলকভাবে প্যাডিং করা সমস্ত পরিবেশে সেই edge case এড়িয়ে যায়। এখানে একটি পুনর্ব্যবহারযোগ্য সংস্করণ যা header এবং payload উভয়কে পৃথক অবজেক্টে ডিকোড করে:

JavaScript — decode header and payload
function decodeBase64Url(segment) {
  const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
  const padded = base64.padEnd(base64.length + (4 - (base64.length % 4)) % 4, "=");
  const binary = atob(padded);
  const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0));
  return new TextDecoder("utf-8").decode(bytes);
}

function decodeJwt(token) {
  const [headerB64, payloadB64] = token.split(".");
  return {
    header: JSON.parse(decodeBase64Url(headerB64)),
    payload: JSON.parse(decodeBase64Url(payloadB64)),
  };
}

const { header, payload } = decodeJwt(token);
console.log("Algorithm:", header.alg);   // "HS256"
console.log("Subject:", payload.sub);     // "usr_921f"
console.log("Role:", payload.role);       // "admin"

এই দুটি ফাংশন পাওয়ার পরে, লজিকটি ফাইলে কপি-পেস্ট করার পরিবর্তে একটি shared utility মডিউলে রাখাই ভালো। একটি src/lib/jwt.ts বা utils/jwt-decode.ts ফাইল একটি typed return shape সহ codebase জুড়ে উদ্দেশ্য স্পষ্ট করে। TypeScript-এ, আপনি return টাইপ করতে পারেন { header: JwtHeader; payload: JwtPayload } হিসাবে যেখানে JwtHeader এর মধ্যে alg, typ, এবং ঐচ্ছিক kid আছে, এবং JwtPayload RFC 7519 নিবন্ধিত claims-এর সাথে কাস্টম claims-এর জন্য একটি index signature সহ extend করে। ডিকোড লজিক কেন্দ্রীভূত করার মানে হলো যখন আপনি পরে error handling (বিকৃত সেগমেন্ট ধরা) বা telemetry (decode ব্যর্থতা লগ করা) যোগ করতে চান, তখন আপনার শুধু একটি জায়গা আপডেট করতে হবে।

দ্রষ্টব্য:TextDecoder ধাপটিই এই পাইপলাইনকে non-ASCII claims-এর জন্য নিরাপদ করে। এটি ছাড়া, atob() একটি Latin-1 স্ট্রিং রিটার্ন করে যেখানে মাল্টি-বাইট UTF-8 সিকোয়েন্স অক্ষরে বিভক্ত হয়। ইমোজি বা CJK টেক্সটের পরিবর্তে আপনি বিকৃত অক্ষর দেখবেন। সর্বদা atob()-এর পরে new TextDecoder("utf-8") দিয়ে পাঠান।

মাল্টি-বাইট অক্ষর সহ UTF-8 JWT Claims ডিকোড করা

JWT payload হলো base64url হিসাবে এনকোড করা UTF-8 JSON। বেশিরভাগ payload-এ শুধু ASCII-only ক্ষেত্র যেমন user ID এবং টাইমস্ট্যাম্প থাকে, তাই ডেভেলপাররা কখনো লক্ষ্য করেন না যে atob() UTF-8-এর পরিবর্তে Latin-1 রিটার্ন করে। সমস্যা দেখা দেয় যখন কোনো claim-এ ইমোজি, জাপানি অক্ষর, সিরিলিক, বা U+00FF-এর উপরে যেকোনো কোড পয়েন্ট থাকে। JavaScript decode UTF-8 প্যাটার্নে বাইনারি স্ট্রিংটি আগে বাইট অ্যারেতে রূপান্তর করতে হবে, তারপর TextDecoder দিয়ে চালাতে হবে।

JavaScript — UTF-8 round-trip with emoji in JWT payload
// Simulating a JWT payload with emoji and CJK characters
const payloadObj = {
  sub: "usr_e821",
  display_name: "田中太郎",
  team: "Platform 🚀",
  region: "ap-northeast-1"
};

// Encode: object → JSON → UTF-8 bytes → base64url
const jsonStr = JSON.stringify(payloadObj);
const utf8Bytes = new TextEncoder().encode(jsonStr);
const base64 = btoa(String.fromCharCode(...utf8Bytes))
  .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");

// Decode: base64url → base64 → binary string → bytes → UTF-8 string
const base64Std = base64.replace(/-/g, "+").replace(/_/g, "/");
const binary = atob(base64Std);
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
const decoded = new TextDecoder("utf-8").decode(bytes);
const result = JSON.parse(decoded);

console.log(result.display_name); // "田中太郎" — সঠিক
console.log(result.team);         // "Platform 🚀" — সঠিক

পুরনো codebase-এ একটি legacy fallback প্যাটার্ন দেখবেন যা decodeURIComponent কে পার্সেন্ট-এনকোডিং কৌশলের সাথে একত্রিত করে। এই JavaScript decodeURIComponent পদ্ধতি কাজ করে কারণ এটি প্রতিটি বাইটকে পার্সেন্ট-হেক্স জোড়া হিসাবে পুনরায় এনকোড করে, তারপর decodeURIComponent মাল্টি-বাইট UTF-8 সিকোয়েন্সগুলি পুনর্গঠন করে:

JavaScript — decodeURIComponent fallback for UTF-8
function decodeBase64UrlLegacy(segment) {
  const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
  const binary = atob(base64);
  // Convert each char to %XX hex, then decodeURIComponent reassembles UTF-8
  const utf8 = decodeURIComponent(
    binary.split("").map(c =>
      "%" + c.charCodeAt(0).toString(16).padStart(2, "0")
    ).join("")
  );
  return utf8;
}

// Works for non-ASCII claims without TextDecoder
const payload = decodeBase64UrlLegacy(token.split(".")[1]);
console.log(JSON.parse(payload));
সতর্কতা:পুরনো JWT utility স্নিপেটে decodeURIComponent(escape(atob(segment))) প্যাটার্ন দেখতে পাবেন। escape() ফাংশনটি deprecated এবং non-standard। উপরে দেখানো TextDecoder পদ্ধতি দিয়ে এটি প্রতিস্থাপন করুন। JavaScript unescape decoder প্যাটার্নে একই সমস্যা আছে: unescape() deprecated। উভয় ফাংশন ভবিষ্যতের JavaScript ইঞ্জিন থেকে সরানো হতে পারে।

JWT Decode পাইপলাইন — ধাপের রেফারেন্স

ব্রাউজার-নেটিভ JWT decode পাইপলাইনের প্রতিটি ধাপ, ব্যবহৃত JavaScript API এবং এটি কী উৎপন্ন করে তা সহ:

প্যারামিটার / ধাপ
ধরন
বিবরণ
token.split(".")
string[]
JWT-কে [header, payload, signature] সেগমেন্টে বিভক্ত করে
base64url → base64
string replace
- কে + দিয়ে, _ কে / দিয়ে প্রতিস্থাপন করুন, 4-এর গুণিতক না হওয়া পর্যন্ত = দিয়ে প্যাড করুন
atob(base64)
string
একটি স্ট্যান্ডার্ড Base64 স্ট্রিংকে বাইনারি স্ট্রিং (Latin-1) এ ডিকোড করে
TextDecoder("utf-8")
TextDecoder
কাঁচা বাইটের Uint8Array কে সঠিক UTF-8 স্ট্রিংয়ে রূপান্তর করে
JSON.parse()
object
ফলস্বরূপ JSON স্ট্রিংকে একটি JavaScript অবজেক্টে পার্স করে

Node.js সমতুল্য ধাপ ২ থেকে ৪ একটি একক কলে সংকুচিত করে: Buffer.from(segment, "base64url").toString("utf-8") "base64url" এনকোডিং অপশন অভ্যন্তরীণভাবে বর্ণমালা রূপান্তর এবং প্যাডিং পরিচালনা করে।

Buffer.from() — JWT-এর জন্য Node.js স্ট্রিং ডিকোডার

Node.js-এর অনেক সহজ পথ আছে। Buffer ক্লাস সরাসরি একটি "base64url" এনকোডিং গ্রহণ করে, তাই আপনি ম্যানুয়াল অক্ষর প্রতিস্থাপন এবং প্যাডিং এড়িয়ে যেতে পারেন। এটি সার্ভার-সাইড কোডের জন্য JavaScript string decoder পথ। একটি লাইন একটি JWT সেগমেন্টকে UTF-8 স্ট্রিংয়ে পরিণত করে, এবং এটি কোনো অতিরিক্ত ধাপ ছাড়াই মাল্টি-বাইট অক্ষরগুলি সঠিকভাবে পরিচালনা করে।

Node.js 18+ — JWT decode with Buffer
const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsIm9yZyI6ImFjbWUtY29ycCIsInJvbGUiOiJiaWxsaW5nIiwiaWF0IjoxNzExNjEwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";

function decodeJwt(jwt) {
  const segments = jwt.split(".");
  return {
    header: JSON.parse(Buffer.from(segments[0], "base64url").toString("utf-8")),
    payload: JSON.parse(Buffer.from(segments[1], "base64url").toString("utf-8")),
  };
}

const { header, payload } = decodeJwt(token);
console.log(header);
// { alg: "HS256" }
console.log(payload);
// { sub: "usr_921f", org: "acme-corp", role: "billing", iat: 1711610000 }

প্রতিটি Node.js প্রজেক্টে আমি এই পদ্ধতিটিই ব্যবহার করি। এটি সংক্ষিপ্ত, দ্রুত, এবং ইতিমধ্যে UTF-8 সঠিকভাবে পরিচালনা করে। কোনো TextDecoder প্রয়োজন নেই, কোনো অক্ষর প্রতিস্থাপন নেই, কোনো প্যাডিং গণিত নেই। Buffer ক্লাস হলো একটি JavaScript string decoder যা base64url বর্ণমালা নেটিভভাবে পরিচালনা করে, যা অক্ষর প্রতিস্থাপন সম্পর্কিত বাগের একটি সম্পূর্ণ শ্রেণি দূর করে। আপনার কোড ব্রাউজার এবং Node.js উভয়ে চলতে হলে, isomorphic wrapper ফাংশনের জন্য নিচের FAQ দেখুন যা রানটাইমে পরিবেশ সনাক্ত করে।

এখানে একটি আরও সম্পূর্ণ উদাহরণ যা সাধারণ JWT claims বের করে এবং টাইমস্ট্যাম্পগুলিকে পাঠযোগ্য তারিখে রূপান্তর করে, যা middleware এবং API route handler-এ আপনি সবচেয়ে বেশি ব্যবহার করবেন:

Node.js — practical JWT claim extraction
function inspectToken(token) {
  const segments = token.split(".");
  if (segments.length !== 3) {
    throw new Error("Not a valid JWT — expected 3 dot-separated segments");
  }

  const header = JSON.parse(Buffer.from(segments[0], "base64url").toString("utf-8"));
  const payload = JSON.parse(Buffer.from(segments[1], "base64url").toString("utf-8"));

  const inspection = {
    algorithm: header.alg,
    tokenType: header.typ || "JWT",
    subject: payload.sub,
    issuer: payload.iss || "(not set)",
    audience: payload.aud || "(not set)",
    issuedAt: payload.iat ? new Date(payload.iat * 1000).toISOString() : "(not set)",
    expiresAt: payload.exp ? new Date(payload.exp * 1000).toISOString() : "(never)",
    isExpired: payload.exp ? payload.exp < Math.floor(Date.now() / 1000) : false,
    customClaims: Object.keys(payload).filter(
      k => !["sub", "iss", "aud", "iat", "exp", "nbf", "jti"].includes(k)
    ),
  };

  return inspection;
}

console.log(inspectToken(process.env.ACCESS_TOKEN));
// {
//   algorithm: "RS256",
//   tokenType: "JWT",
//   subject: "usr_921f",
//   issuer: "https://auth.internal",
//   audience: "billing-api",
//   issuedAt: "2026-03-10T14:00:00.000Z",
//   expiresAt: "2026-03-10T15:00:00.000Z",
//   isExpired: true,
//   customClaims: ["role", "scope", "org"]
// }

প্রোডাকশন Node.js সার্ভিসে, Buffer.from() ডিকোড প্যাটার্ন তিনটি পুনরাবৃত্ত জায়গায় দেখা যায়। প্রথমটি হলো request logging middleware: আপনি auth সার্ভারে অতিরিক্ত নেটওয়ার্ক রাউন্ড-ট্রিপ ছাড়াই প্রতিটি structured log entry-তে userId এবং org সংযুক্ত করতে আসা Authorization header ডিকোড করেন। দ্বিতীয়টি হলো ডিবাগিং: পরীক্ষার assertion লেখার আগে সঠিক scopes ইস্যু হয়েছে কিনা নিশ্চিত করতে ডেভেলপমেন্টের সময় console-এ ডিকোড করা token claims প্রিন্ট করুন। তৃতীয়টি হলো API gateway-তে proactive token refresh। Token upstream forward করে downstream সার্ভিসকে মাঝপথে token মেয়াদ শেষ হলে 401 রিটার্ন করতে না দিয়ে, gateway edge-এ token ডিকোড করে, exp claim পড়ে, এবং পরবর্তী 30 সেকেন্ডের মধ্যে মেয়াদ শেষ হলে refresh ট্রিগার করে। এটি ক্ষণস্থায়ী auth ব্যর্থতার একটি শ্রেণি দূর করে যা পুনরুৎপাদন করা কঠিন এবং ডিবাগ করা হতাশাজনক।

দ্রষ্টব্য:"base64url" এনকোডিং Node.js 15.7.0-এ যোগ করা হয়েছিল। আপনি যদি Node.js 14 বা তার আগের ভার্সনে আটকে থাকেন, তাহলে Buffer.from(segment.replace(/-/g, "+").replace(/_/g, "/"), "base64") তে ফিরে যান যা একইভাবে কাজ করে কিন্তু ম্যানুয়াল অক্ষর swap প্রয়োজন।

ফাইল এবং API রেসপন্স থেকে JWT ডিকোড করা

দুটি পরিস্থিতি ঘন ঘন আসে। প্রথমটি হলো একটি লোকাল ফাইল থেকে JWT পড়া: ডেভেলপমেন্টের সময় সংরক্ষিত token, একটি test fixture, বা post-mortem বিশ্লেষণের জন্য একটি ঘটনার সময় dump করা ফাইল। দ্বিতীয়টি হলো HTTP রেসপন্স থেকে JWT বের করা, সাধারণত OAuth token রেসপন্স বডিতে access_token ক্ষেত্র বা একটি Authorization header। উভয়ের জন্যই error handling প্রয়োজন কারণ বিকৃত token, ছেঁটে ফেলা ফাইল, এবং নেটওয়ার্ক ত্রুটি প্রতিদিনের বাস্তবতা। গত সপ্তাহে বৈধ একটি token trailing whitespace বা কপি-পেস্ট থেকে newline থাকতে পারে। auth সার্ভার error পেজ রিটার্ন করলে রেসপন্স বডি JSON-এর পরিবর্তে HTML হতে পারে।

ফাইল থেকে JWT পড়া (Node.js)

Node.js — decode JWT from file with error handling
import { readFileSync } from "node:fs";

function decodeJwtFromFile(filePath) {
  const raw = readFileSync(filePath, "utf-8").trim();
  const segments = raw.split(".");

  if (segments.length !== 3) {
    throw new Error(`Invalid JWT: expected 3 segments, got ${segments.length}`);
  }

  try {
    return {
      header: JSON.parse(Buffer.from(segments[0], "base64url").toString("utf-8")),
      payload: JSON.parse(Buffer.from(segments[1], "base64url").toString("utf-8")),
    };
  } catch (err) {
    throw new Error(`Failed to decode JWT from ${filePath}: ${err.message}`);
  }
}

try {
  const { header, payload } = decodeJwtFromFile("./test-fixtures/access-token.txt");
  console.log("Algorithm:", header.alg);
  console.log("Expires:", new Date(payload.exp * 1000).toISOString());
} catch (err) {
  console.error(err.message);
}

API রেসপন্স থেকে JWT বের করা (fetch)

JavaScript — decode JWT from API response
async function fetchAndDecodeToken(loginUrl, credentials) {
  const response = await fetch(loginUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(credentials),
  });

  if (!response.ok) {
    throw new Error(`Login failed: ${response.status} ${response.statusText}`);
  }

  const { access_token } = await response.json();
  if (!access_token || access_token.split(".").length !== 3) {
    throw new Error("Response does not contain a valid JWT");
  }

  const payload = access_token.split(".")[1];
  const json = Buffer.from(payload, "base64url").toString("utf-8");
  return JSON.parse(json);
}

// Usage
try {
  const claims = await fetchAndDecodeToken(
    "https://auth.internal/oauth/token",
    { username: "deploy-bot", password: process.env.DEPLOY_TOKEN }
  );
  console.log("Token subject:", claims.sub);
  console.log("Token scopes:", claims.scope);
  console.log("Expires at:", new Date(claims.exp * 1000).toISOString());
} catch (err) {
  console.error("Token decode error:", err.message);
}

কমান্ড-লাইনে JWT ডিকোডিং

কখনো কখনো আপনি শুধু স্ক্রিপ্ট না লিখেই টার্মিনাল থেকে একটি token দেখতে চান। Node.js বেশিরভাগ ডেভেলপার মেশিনে পাওয়া যায়, তাই একটি one-liner ভালো কাজ করে। jq সুন্দর-প্রিন্টিং পরিচালনা করে।

bash — decode JWT payload from terminal
# Decode JWT payload with Node.js one-liner
echo "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsInJvbGUiOiJhZG1pbiJ9.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" \
  | cut -d. -f2 \
  | node -e "process.stdin.on('data', d => console.log(JSON.parse(Buffer.from(d.toString().trim(), 'base64url').toString('utf-8'))))"

# Pipe to jq for pretty output
echo "$JWT_TOKEN" | cut -d. -f2 \
  | node -e "process.stdin.on('data', d => process.stdout.write(Buffer.from(d.toString().trim(), 'base64url').toString('utf-8')))" \
  | jq .

# Decode both header and payload
echo "$JWT_TOKEN" | node -e "
  process.stdin.on('data', d => {
    const parts = d.toString().trim().split('.');
    console.log('Header:', JSON.parse(Buffer.from(parts[0], 'base64url').toString()));
    console.log('Payload:', JSON.parse(Buffer.from(parts[1], 'base64url').toString()));
  });
"

Node.js ছাড়া pure bash পছন্দ করলে, tr দিয়ে base64url অক্ষরগুলি ঠিক করার পরে base64 -d দিয়ে সেগমেন্ট পাইপ করুন:

bash — pure bash JWT decode without Node.js
# Pure bash: decode JWT payload without Node.js
echo "$JWT_TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq .

# macOS variant (base64 -D instead of -d)
echo "$JWT_TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -D 2>/dev/null | jq .

কোনো টার্মিনাল ছাড়াই দ্রুত ভিজ্যুয়াল পরীক্ষার জন্য, আপনার token টি ToolDeck JWT Decoder তে পেস্ট করুন রঙ-কোড করা claim লেবেল এবং মেয়াদ শেষের স্ট্যাটাস সহ তিনটি সেগমেন্টের পাশাপাশি বিশ্লেষণের জন্য।

jose — একটি লাইব্রেরিতে যাচাই এবং ডিকোডিং

প্রোডাকশন authentication middleware-এর জন্য, শুধু ডিকোডিং নয়, সিগনেচার যাচাই প্রয়োজন। jose লাইব্রেরি সেরা বিকল্প। এটি Node.js এবং ব্রাউজার উভয়ে (Web Crypto API এর মাধ্যমে) কাজ করে, HS256, RS256, ES256, EdDSA, এবং JWE (এনক্রিপ্টেড token) সমর্থন করে, এবং শূন্য native dependency আছে। npm install jose দিয়ে ইনস্টল করুন।

JavaScript — jose: verify HS256 token
import * as jose from "jose";

const secret = new TextEncoder().encode("k8s-webhook-signing-secret-2026");
const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsInNjb3BlIjoiYmlsbGluZzpyZWFkIiwiaWF0IjoxNzExNjEwMDAwLCJleHAiOjE3MTE2MTM2MDB9.abc123";

try {
  const { payload, protectedHeader } = await jose.jwtVerify(token, secret);
  console.log("Algorithm:", protectedHeader.alg); // "HS256"
  console.log("Subject:", payload.sub);            // "usr_921f"
  console.log("Scope:", payload.scope);            // "billing:read"
} catch (err) {
  if (err.code === "ERR_JWT_EXPIRED") {
    console.error("Token expired at:", err.payload.exp);
  } else {
    console.error("Verification failed:", err.message);
  }
}
JavaScript — jose: verify RS256 with JWKS endpoint
import * as jose from "jose";

// Fetch the public key set from the identity provider
const jwks = jose.createRemoteJWKSet(
  new URL("https://auth.internal/.well-known/jwks.json")
);

const token = req.headers.authorization?.split(" ")[1];
if (!token) {
  return res.status(401).json({ error: "Missing token" });
}

try {
  const { payload } = await jose.jwtVerify(token, jwks, {
    issuer: "https://auth.internal",
    audience: "billing-api",
  });
  // payload.sub, payload.scope, etc. are now verified
  req.userId = payload.sub;
} catch (err) {
  return res.status(401).json({ error: "Invalid token" });
}

jose এবং পুরনো jsonwebtoken প্যাকেজের মধ্যে সিদ্ধান্ত নেওয়ার সময়, মূল পার্থক্য হলো runtime scope। jsonwebtoken শুধুমাত্র Node.js — এটি crypto built-in-এর উপর নির্ভর করে এবং ব্রাউজারের জন্য bundle হবে না। jose সম্পূর্ণ isomorphic: এটি Web Crypto API ব্যবহার করে, যা সমস্ত আধুনিক ব্রাউজার, Node.js 16+, Deno, Bun, এবং Cloudflare Workers-এ পাওয়া যায়। আপনার auth লজিক একটি Next.js middleware ফাইলে (Edge Runtime-এ চলে), বা Cloudflare Worker-এ, বা সার্ভার এবং ক্লায়েন্ট কোড উভয় দ্বারা import করা shared utility-তে থাকলে, jose সঠিক পছন্দ কারণ এর শূন্য native dependency আছে এবং build step ছাড়াই ইনস্টল হয়। jsonwebtoken pure Node.js সার্ভার অ্যাপ্লিকেশনের জন্য যুক্তিসঙ্গত থাকে যেখানে আপনার signing helper-এর বিস্তৃত ইকোসিস্টেম প্রয়োজন এবং edge পরিবেশে কোড চালানোর পরিকল্পনা নেই। 2026 সালে একটি নতুন প্রজেক্টে, পুরনো API পছন্দ করার বিশেষ কারণ না থাকলে ডিফল্টভাবে jose ব্যবহার করুন।

শুধু যাচাই ছাড়া decode প্রয়োজন হলে, jose jose.decodeJwt(token) প্রদান করে যা payload রিটার্ন করে এবং header-এর জন্য jose.decodeProtectedHeader(token)। এগুলি convenience ফাংশন যা অভ্যন্তরীণভাবে Base64url ডিকোডিং করে। কিন্তু jose-এর কাছে পৌঁছানোর পুরো কারণ হলো আপনার সাধারণত decode ছাড়া verify-ও করা উচিত। ক্লায়েন্ট সাইডে token claims থেকে ব্যবহারকারীর নিজের display name বা avatar URL দেখাতে হলে, decode-only ঠিক আছে। সার্ভার সাইডে সর্বদা verify করুন। আমি প্রোডাকশন সিস্টেম দেখেছি যা সিগনেচার না পরীক্ষা করেই access control সিদ্ধান্তের জন্য JWT claims ডিকোড করেছিল, এবং এটি JWT ফরম্যাট বোঝে এমন যেকোনো আক্রমণকারীর জন্য একটি খোলা দরজা।

JavaScript — jose.decodeJwt for decode-only scenarios
import * as jose from "jose";

// Decode-only: no secret needed, no verification
const payload = jose.decodeJwt(token);
console.log(payload.sub);   // "usr_921f"
console.log(payload.scope); // "billing:read"

const header = jose.decodeProtectedHeader(token);
console.log(header.alg);    // "HS256"
console.log(header.typ);    // "JWT"

// Check expiry without verification (client-side display)
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
  console.log("Token has expired — redirect to login");
}

সিনট্যাক্স হাইলাইটিং সহ টার্মিনাল আউটপুট

Node.js CLI টুলে বা একটি ঘটনার সময় JWT token ডিবাগ করার সময়, রঙ-কোড করা আউটপুট সত্যিকারের পার্থক্য করে। chalk লাইব্রেরি JSON.stringify এর সাথে মিলিয়ে কাজটি করে। npm install chalk দিয়ে ইনস্টল করুন।

Node.js — colorized JWT decode output
import chalk from "chalk";

function printJwt(token) {
  const segments = token.split(".");
  if (segments.length !== 3) {
    console.error(chalk.red("Invalid JWT: expected 3 segments"));
    return;
  }

  const header = JSON.parse(Buffer.from(segments[0], "base64url").toString("utf-8"));
  const payload = JSON.parse(Buffer.from(segments[1], "base64url").toString("utf-8"));

  console.log(chalk.bold.cyan("\n=== JWT Header ==="));
  console.log(chalk.gray(JSON.stringify(header, null, 2)));

  console.log(chalk.bold.green("\n=== JWT Payload ==="));
  console.log(chalk.gray(JSON.stringify(payload, null, 2)));

  // Highlight expiration status
  if (payload.exp) {
    const expiresAt = new Date(payload.exp * 1000);
    const isExpired = expiresAt < new Date();
    console.log(
      chalk.bold("\nExpires:"),
      isExpired
        ? chalk.red(`EXPIRED at ${expiresAt.toISOString()}`)
        : chalk.green(`Valid until ${expiresAt.toISOString()}`)
    );
  }

  console.log(chalk.dim("\nSignature: " + segments[2].substring(0, 20) + "..."));
}

printJwt(process.argv[2]);
// Run: node jwt-debug.mjs "eyJhbGci..."
দ্রষ্টব্য:রঙ আউটপুট শুধুমাত্র টার্মিনালের জন্য। log ফাইল, API রেসপন্স, বা ডেটাবেস ক্ষেত্রে JWT claims লেখার সময় chalk ব্যবহার করবেন না। ANSI escape কোডগুলি non-terminal পরিবেশে বিকৃত অক্ষর হিসাবে দেখাবে।

বড় লগ ফাইল থেকে JWT প্রক্রিয়া করা

আধুনিক API অবকাঠামো NDJSON ফরম্যাটে structured access log তৈরি করে — প্রতি লাইনে একটি JSON অবজেক্ট, প্রতিটি লাইনে request path, response status, latency, এবং ডিকোড করা বা কাঁচা Authorization header থাকে। ব্যস্ত সার্ভিসে এই ফাইলগুলি দ্রুত বাড়ে: প্রতি মিনিটে 10,000 request পরিচালনা করা একটি gateway প্রতিদিন 14 মিলিয়নেরও বেশি log entry তৈরি করে। নিরাপত্তা এবং compliance ব্যবহারের ক্ষেত্রে নিয়মিত পরে এই ফাইলগুলি স্ক্যান করতে হয় — একটি আপোসকৃত service account দ্বারা প্রতিটি request চিহ্নিত করা (post-incident বিশ্লেষণ), একটি নির্দিষ্ট ব্যবহারকারীর token ডেটা-অ্যাক্সেস উইন্ডোর আগে মেয়াদ শেষ হয়েছে কিনা নিশ্চিত করা (compliance audit), বা maintenance window-এ একটি sensitive endpoint অ্যাক্সেস করা সমস্ত subject বের করা। যেহেতু একটি একক log ফাইল কয়েক গিগাবাইট ছাড়িয়ে যেতে পারে, readFileSync দিয়ে মেমোরিতে লোড করা কার্যকর নয়। Node.js readline stream ফাইলটি একটি সময়ে এক লাইন প্রক্রিয়া করে ধ্রুবক মেমোরি overhead সহ, একটি স্ট্যান্ডার্ড ডেভেলপার ল্যাপটপে যেকোনো বড় log স্ক্যান করা ব্যবহারিক করে তোলে।

পৃথক JWT-এর সাথে আপনি "ফাইল মেমোরির জন্য অনেক বড়" সমস্যায় পড়বেন না, যেহেতু একটি একক token খুব কমই কয়েক কিলোবাইটের বেশি। যে পরিস্থিতি আসে তা হলো JWT token-এর জন্য একটি বড় access log বা audit trail স্ক্যান করা, প্রতিটি ডিকোড করা, এবং নির্দিষ্ট claims বের করা। Node.js stream পুরো ফাইল লোড না করেই এটি পরিচালনা করে।

Node.js — stream NDJSON log and decode embedded JWTs
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";

async function scanLogsForExpiredTokens(logPath) {
  const fileStream = createReadStream(logPath, { encoding: "utf-8" });
  const rl = createInterface({ input: fileStream, crlfDelay: Infinity });

  let lineCount = 0;
  let expiredCount = 0;
  const nowSeconds = Math.floor(Date.now() / 1000);

  for await (const line of rl) {
    lineCount++;
    try {
      const entry = JSON.parse(line);
      if (!entry.authorization_token) continue;

      const segments = entry.authorization_token.split(".");
      if (segments.length !== 3) continue;

      const payload = JSON.parse(
        Buffer.from(segments[1], "base64url").toString("utf-8")
      );

      if (payload.exp && payload.exp < nowSeconds) {
        expiredCount++;
        const expDate = new Date(payload.exp * 1000).toISOString();
        console.log("Line " + lineCount + ": expired token for " + payload.sub + ", exp=" + expDate);
      }
    } catch {
      // Skip malformed lines
    }
  }

  console.log(`\nScanned ${lineCount} lines, found ${expiredCount} expired tokens`);
}

scanLogsForExpiredTokens("./logs/api-access-2026-03.ndjson");
Node.js — extract unique JWT subjects from log stream
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";

async function extractUniqueSubjects(logPath) {
  const rl = createInterface({
    input: createReadStream(logPath, { encoding: "utf-8" }),
    crlfDelay: Infinity,
  });

  const subjects = new Set();
  const jwtRegex = /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;

  for await (const line of rl) {
    const matches = line.match(jwtRegex);
    if (!matches) continue;

    for (const token of matches) {
      try {
        const payload = JSON.parse(
          Buffer.from(token.split(".")[1], "base64url").toString("utf-8")
        );
        if (payload.sub) subjects.add(payload.sub);
      } catch {
        // Not a valid JWT
      }
    }
  }

  console.log(`Found ${subjects.size} unique subjects:`);
  for (const sub of subjects) console.log(`  ${sub}`);
}

extractUniqueSubjects("./logs/gateway-2026-03.log");
দ্রষ্টব্য:log ফাইল 50 MB ছাড়িয়ে গেলে streaming-এ স্যুইচ করুন। readFileSync দিয়ে একটি 500 MB NDJSON ফাইল লোড করলে মেমোরি আটকে যাবে এবং GC pause ট্রিগার হবে। readline পদ্ধতি ধ্রুবক মেমোরি ব্যবহারে একটি সময়ে এক লাইন প্রক্রিয়া করে।

সাধারণ ভুল

non-ASCII claims-এর জন্য TextDecoder ছাড়া atob() ব্যবহার করা

সমস্যা: atob() একটি Latin-1 স্ট্রিং রিটার্ন করে। মাল্টি-বাইট UTF-8 অক্ষর (ইমোজি, CJK, উচ্চারণ চিহ্ন সহ অক্ষর) অক্ষরে বিভক্ত হয়ে বিকৃত হয়ে আসে।

সমাধান: atob() আউটপুটকে Uint8Array-এ রূপান্তর করুন, তারপর new TextDecoder('utf-8') দিয়ে পাঠান।

Before · JavaScript
After · JavaScript
// Breaks on non-ASCII payload claims
const payload = JSON.parse(atob(token.split(".")[1]));
// display_name appears as "ç°ä¸­å¤ªé\x83\x8E" instead of "田中太郎"
const binary = atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"));
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
const payload = JSON.parse(new TextDecoder("utf-8").decode(bytes));
// display_name correctly shows "田中太郎"
base64url থেকে base64 অক্ষর প্রতিস্থাপন ভুলে যাওয়া

সমস্যা: atob() "InvalidCharacterError" থ্রো করে কারণ base64url + এবং / এর পরিবর্তে - এবং _ ব্যবহার করে।

সমাধান: atob() কল করার আগে - কে + দিয়ে এবং _ কে / দিয়ে প্রতিস্থাপন করুন। Node.js Buffer.from() 'base64url' সহ এটি স্বয়ংক্রিয়ভাবে পরিচালনা করে।

Before · JavaScript
After · JavaScript
// Throws: InvalidCharacterError: String contains an invalid character
const payload = atob(token.split(".")[1]);
const segment = token.split(".")[1];
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
const payload = atob(base64); // now works
সিগনেচার যাচাই ছাড়া ডিকোড করা JWT claims বিশ্বাস করা

সমস্যা: যে কেউ যেকোনো payload সহ JWT তৈরি করতে পারে। ডিকোডিং শুধু ডেটা পড়ে — এটি প্রমাণ করে না যে token আপনার auth সার্ভার দ্বারা ইস্যু করা হয়েছিল।

সমাধান: সার্ভার সাইডে সর্বদা jose.jwtVerify() বা jsonwebtoken.verify() ব্যবহার করে সিগনেচার যাচাই করুন। ব্যবহারকারীর claims প্রদর্শনের জন্য ক্লায়েন্ট সাইডে decode-only গ্রহণযোগ্য।

Before · JavaScript
After · JavaScript
// DANGEROUS: decoded but not verified
const claims = JSON.parse(atob(token.split(".")[1]));
if (claims.role === "admin") {
  grantAdminAccess(); // attacker can forge this
}
import * as jose from "jose";
const { payload } = await jose.jwtVerify(token, secretKey);
if (payload.role === "admin") {
  grantAdminAccess(); // safe — signature is verified
}
1000 দিয়ে ভাগ না করে exp-কে Date.now()-এর সাথে তুলনা করা

সমস্যা: JWT exp হলো epoch থেকে সেকেন্ড, কিন্তু Date.now() মিলিসেকেন্ড রিটার্ন করে। তুলনা সবসময় বলবে token বৈধ কারণ মিলিসেকেন্ড টাইমস্ট্যাম্প 1000 গুণ বড়।

সমাধান: exp-এর সাথে তুলনা করার আগে Date.now() কে 1000 দিয়ে ভাগ করুন এবং ফলাফল floor করুন।

Before · JavaScript
After · JavaScript
// Bug: Date.now() is milliseconds, exp is seconds
if (payload.exp > Date.now()) {
  console.log("Token is valid"); // always true — wrong!
}
const nowSeconds = Math.floor(Date.now() / 1000);
if (payload.exp > nowSeconds) {
  console.log("Token is valid"); // correct comparison
}

JWT ডিকোড পদ্ধতি — দ্রুত তুলনা

পদ্ধতি
পরিবেশ
UTF-8 নিরাপদ
সিগনেচার যাচাই
কাস্টম টাইপ
ইনস্টল প্রয়োজন
atob() + TextDecoder
ব্রাউজার
N/A (শুধু পড়া)
না
Buffer.from()
Node.js
N/A (শুধু পড়া)
না
decodeURIComponent()
ব্রাউজার (পুরানো)
N/A (শুধু পড়া)
না
jose
উভয়
✓ (JWS/JWE)
npm install
jsonwebtoken
Node.js
npm install
jwt-decode
উভয়
N/A
npm install

ব্যবহারকারীর কাছে claims প্রদর্শনের জন্য ব্রাউজার-সাইড decode-এর সময় atob() + TextDecoder ব্যবহার করুন। Node.js স্ক্রিপ্ট এবং CLI টুলে Buffer.from() ব্যবহার করুন। যখনই সিগনেচার যাচাই প্রয়োজন, যা যেকোনো সার্ভার-সাইড auth middleware-এ, jose ব্যবহার করুন। jwt-decode প্যাকেজ হলো একটি হালকা বিকল্প যদি আপনি ব্রাউজারে decode-only-র জন্য one-function API চান। কোড না লিখে দ্রুত ভিজ্যুয়াল পরীক্ষার জন্য, আপনার token টি JWT Decoder টুল-এ পেস্ট করুন।

প্রায়শই জিজ্ঞাসিত প্রশ্ন

লাইব্রেরি ছাড়া JavaScript-এ JWT টোকেন কীভাবে ডিকোড করব?

টোকেনটিকে "." দিয়ে বিভক্ত করুন, দ্বিতীয় সেগমেন্ট (payload) নিন, - কে + দিয়ে এবং _ কে / দিয়ে প্রতিস্থাপন করে base64url এনকোডিং স্বাভাবিক করুন, = অক্ষর দিয়ে প্যাড করুন, তারপর atob() কল করুন এবং এরপর TextDecoder দিয়ে UTF-8 JSON স্ট্রিং পান। ফলাফলটি JSON.parse() দিয়ে পাঠান এবং আপনার কাছে claims অবজেক্ট থাকবে। কোনো npm প্যাকেজ প্রয়োজন নেই। এই পদ্ধতি সমস্ত আধুনিক ব্রাউজারে এবং Node.js 18+-এ কাজ করে। যদি আপনাকে header-ও পড়তে হয়, প্রথম সেগমেন্টে একই ডিকোডিং ধাপ প্রয়োগ করুন। মনে রাখবেন যে এটি কোনো সিগনেচার যাচাই ছাড়াই কাঁচা ডেটা দেয় — সার্ভার-সাইডে সিগনেচার যাচাই না করা পর্যন্ত ফলাফলটি কেবল প্রদর্শনের জন্য ব্যবহার করুন।

JavaScript
const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c3JfOTIxZiIsInJvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
const payload = token.split(".")[1];
const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
const json = atob(base64);
const claims = JSON.parse(json);
console.log(claims);
// { sub: "usr_921f", role: "admin" }

JWT ডিকোডিংয়ের জন্য atob() এবং Buffer.from()-এর মধ্যে পার্থক্য কী?

atob() একটি ব্রাউজার API যা স্ট্যান্ডার্ড Base64 কে Latin-1 বাইনারি স্ট্রিংয়ে ডিকোড করে। এটি সরাসরি base64url এনকোডিং বোঝে না, তাই আপনাকে আগে - এবং _ অক্ষরগুলি প্রতিস্থাপন করতে হবে। Buffer.from(segment, "base64url") একটি Node.js API যা base64url বর্ণমালা সরাসরি হ্যান্ডেল করে এবং একটি Buffer রিটার্ন করে যেখানে .toString("utf-8") কল করা যায়। ব্রাউজারে atob() ব্যবহার করুন, Node.js-এ Buffer.from() ব্যবহার করুন। তৃতীয় বিকল্পটি — যা ধীর কিন্তু ঐতিহাসিকভাবে সাধারণ — হলো decodeURIComponent পার্সেন্ট-এনকোডিং কৌশল, কিন্তু এই প্যাটার্নটি কিছু পুরানো স্নিপেটে deprecated escape() ফাংশনের উপর নির্ভর করে এবং নতুন কোডে এড়ানো উচিত। উভয় পরিবেশে চলে এমন isomorphic কোডের জন্য, typeof Buffer !== "undefined" চেক করুন এবং সেই অনুযায়ী শাখা করুন।

JavaScript
// Browser
const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));

// Node.js
const json2 = Buffer.from(payload, "base64url").toString("utf-8");

non-ASCII JWT claims-এ atob() কেন ভেঙে পড়ে?

atob() একটি Latin-1 স্ট্রিং রিটার্ন করে যেখানে প্রতিটি অক্ষর একটি একক বাইটের সাথে মেলে। মাল্টি-বাইট UTF-8 সিকোয়েন্স (ইমোজি, CJK অক্ষর, Latin-1-এর বাইরে উচ্চারণ চিহ্ন সহ অক্ষর) একাধিক অক্ষরে বিভক্ত হয়ে যায়, যার ফলে বিকৃত আউটপুট আসে। সমাধান হলো বাইনারি স্ট্রিংটি আগে Uint8Array-এ রূপান্তর করা, তারপর সেই অ্যারেটি new TextDecoder("utf-8").decode()-এ পাঠানো। TextDecoder API মাল্টি-বাইট সিকোয়েন্সগুলি সঠিকভাবে পুনর্গঠন করে। এই সমস্যাটি ডেভেলপমেন্টে সহজেই এড়িয়ে যাওয়া যায় কারণ বেশিরভাগ JWT payload-এ শুধু ASCII ইউজার আইডি, টাইমস্ট্যাম্প এবং রোলের নাম থাকে — বাগটি তখনই দেখা দেয় যখন কোনো claim-এ non-ASCII display name বা স্থানীয়করণ করা স্ট্রিং থাকে। বর্তমান payload-গুলি ASCII-only হলেও সর্বদা নতুন কোডে TextDecoder পথ ব্যবহার করুন, কারণ অ্যাপ্লিকেশন বিকশিত হওয়ার সাথে সাথে claim-গুলি পরিবর্তন হতে পারে।

JavaScript
// ভাঙা: atob Latin-1 রিটার্ন করে, মাল্টি-বাইট অক্ষর বিকৃত হয়
const broken = atob(base64); // "ð\x9F\x8E\x89" ইমোজির পরিবর্তে

// ঠিক করা: বাইট অ্যারেতে রূপান্তর করুন, তারপর TextDecoder
const bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
const fixed = new TextDecoder("utf-8").decode(bytes);

JavaScript-এ JWT সিগনেচার যাচাই করা কি সম্ভব?

ডিকোডিং এবং যাচাইকরণ দুটি আলাদা অপারেশন। ডিকোডিং শুধু payload পড়ে, যা এনক্রিপ্টেড নয়। যাচাইকরণ সিক্রেট (HMAC) বা পাবলিক কী (RSA/ECDSA) এর বিপরীতে সিগনেচার পরীক্ষা করে। jose লাইব্রেরি Web Crypto API এর মাধ্যমে ব্রাউজারে এবং Node.js-এ উভয়ই সমর্থন করে। jsonwebtoken প্যাকেজ শুধুমাত্র Node.js-এ কাজ করে। সার্ভার সাইডে সিগনেচার যাচাই না করে কখনই ডিকোড করা claim বিশ্বাস করবেন না। ক্লায়েন্ট সাইডে ব্যবহারকারীর display name বা মেয়াদ শেষের সময় পড়ার জন্য JWT ডিকোড করা গ্রহণযোগ্য, কিন্তু যেকোনো অ্যাক্সেস কন্ট্রোল সিদ্ধান্ত — কোনো ব্যবহারকারীর নির্দিষ্ট ভূমিকা বা অনুমতি আছে কিনা তা যাচাই — যাচাইয়ের পরে সার্ভার-সাইড কোডে হতে হবে। একজন আক্রমণকারী যিনি JWT ফরম্যাট বোঝেন তিনি যেকোনো claim সহ একটি টোকেন তৈরি করতে পারেন এবং আপনার ক্লায়েন্ট-সাইড চেক পাস হয়ে যাবে।

JavaScript
import * as jose from "jose";

const secret = new TextEncoder().encode("your-256-bit-secret");
const { payload } = await jose.jwtVerify(token, secret);
console.log(payload.sub); // verified claims

JavaScript-এ JWT মেয়াদ শেষ হয়েছে কিনা কীভাবে পরীক্ষা করব?

payload ডিকোড করুন এবং exp claim পড়ুন, যা সেকেন্ডে Unix টাইমস্ট্যাম্প। Math.floor(Date.now() / 1000) ব্যবহার করে বর্তমান সময়ের সাথে তুলনা করুন। যদি বর্তমান সময় exp-এর চেয়ে বেশি হয়, টোকেনের মেয়াদ শেষ হয়ে গেছে। মনে রাখবেন: exp মান হলো ইপোক থেকে সেকেন্ড, মিলিসেকেন্ড নয়, তাই Date.now() কে 1000 দিয়ে ভাগ করা আবশ্যক। ব্যবহারিকভাবে, একটি ছোট ক্লক-স্কিউ বাফার তৈরি করুন — টোকেনটি কঠোরভাবে অতীতে মেয়াদ শেষ হয়েছে কিনা তা না দেখে পরবর্তী 30 সেকেন্ডের মধ্যে মেয়াদ শেষ হবে কিনা তা যাচাই করুন, এটি এজ কেস প্রতিরোধ করে যেখানে টোকেনটি ডিকোড করার সময় প্রযুক্তিগতভাবে বৈধ কিন্তু পরবর্তী downstream API কল প্রক্রিয়া করার সময় মেয়াদ শেষ হয়ে যায়। এছাড়াও এমন ক্ষেত্রে হ্যান্ডেল করুন যেখানে exp একেবারে অনুপস্থিত, যার মানে টোকেনটি কখনো মেয়াদ শেষ হয় না।

JavaScript
function isTokenExpired(token) {
  const payload = JSON.parse(
    atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"))
  );
  const nowSeconds = Math.floor(Date.now() / 1000);
  return payload.exp < nowSeconds;
}

console.log(isTokenExpired(myToken)); // true or false

Node.js এবং ব্রাউজার উভয়ে কাজ করে এমন isomorphic JWT decode কোড কীভাবে লিখব?

globalThis.Buffer-এর অস্তিত্ব পরীক্ষা করুন। যদি থাকে, আপনি Node.js-এ আছেন এবং Buffer.from(segment, "base64url").toString("utf-8") ব্যবহার করতে পারবেন। না থাকলে, আপনি ব্রাউজারে আছেন এবং TextDecoder পদ্ধতিসহ atob() ব্যবহার করা উচিত। এই চেকটি একটি একক decodeBase64Url ফাংশনে মুড়িয়ে সর্বত্র ব্যবহার করুন। এটি সবচেয়ে গুরুত্বপূর্ণ ইউটিলিটি প্যাকেজ, ডিজাইন সিস্টেম কম্পোনেন্ট, এবং যেকোনো শেয়ার্ড কোডের জন্য যা একটি monorepo প্যাকেজে থাকে এবং Next.js সার্ভার কম্পোনেন্ট এবং ব্রাউজার React কম্পোনেন্ট উভয় দ্বারা import করা হয়। একটি জায়গায় এনভায়রনমেন্ট ডিটেকশন রাখার মানে হলো রানটাইম পরিবর্তন হলে আপনাকে শুধু একটি জায়গায় আপডেট করতে হবে — উদাহরণস্বরূপ, যখন Deno সম্পূর্ণ Buffer সমর্থন যোগ করে বা একটি নতুন এজ রানটাইমের ভিন্ন কোড পাথের প্রয়োজন হয়।

JavaScript
function decodeBase64Url(segment) {
  if (typeof Buffer !== "undefined") {
    return Buffer.from(segment, "base64url").toString("utf-8");
  }
  const base64 = segment.replace(/-/g, "+").replace(/_/g, "/");
  const bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
  return new TextDecoder("utf-8").decode(bytes);
}

সম্পর্কিত টুলস

এছাড়াও পাওয়া যায়:Python
MW
Marcus WebbJavaScript Performance Engineer

Marcus specialises in JavaScript performance, build tooling, and the inner workings of the V8 engine. He has spent years profiling and optimising React applications, working on bundler configurations, and squeezing every millisecond out of critical rendering paths. He writes about Core Web Vitals, JavaScript memory management, and the tools developers reach for when performance really matters.

SL
Sophie Laurentপ্রযুক্তিগত পর্যালোচক

Sophie is a full-stack developer focused on TypeScript across the entire stack — from React frontends to Express and Fastify backends. She has a particular interest in type-safe API design, runtime validation, and the patterns that make large JavaScript codebases stay manageable. She writes about TypeScript idioms, Node.js internals, and the ever-evolving JavaScript module ecosystem.