إذا رأيتَ رسالة خطأ تبدأ بحروف eyJ، فأنت تتعامل مع JWT. هو الصيغة الأكثر انتشاراً لجلسات الويب وواجهات الـ API، وهو أيضاً أكثر الصيغ التي يساء فهمها. فك الترميز سهل جداً؛ المشكلة أن سهولة القراءة تجعل الناس يظنون أن قراءته تعني التحقق منه، وهذا بالتحديد هو الخطأ الذي ينتج عنه تطبيقات غير آمنة.

بنية التوكن: ثلاث نقاط فقط

أي JWT يتكوّن من ثلاثة أجزاء مفصولة بنقاط، وكل جزء بترميز base64url. لا يوجد جزء رابع ولا خامس:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE3MDAwMDAwMDB9.abc123signaturetext
HEADER   .  PAYLOAD   .  SIGNATURE
    ↓           ↓          ↓
 الخوارزمية   المطالبات   التوقيع
 (alg)        (claims)   (تحقق)

الـ header يخبر الخادم بخوارزمية التوقيع المستخدمة، والـ payload يحمل البيانات المعروفة باسم المطالبات، والـ signature هو ما يحمي المحتوى من التعديل بعد إصداره.

الجزءماذا يحتويماذا لا يحتوي
Headerالخوارزمية ونوع التوكنأي سرّ — أبداً
Payloadالمطالبات: المعرّف والصلاحيات والانتهاءكلمات مرور أو بيانات حسّاسة
Signatureدليل أن المحتوى لم يُعدَّلالتشفير — هو تحقّق لا سرّية

أكبر سوء فهم: base64 ليس تشفيراً

ما تجريه في أداة أو في الطرفية هو ترميز عكسي، لا كسر لأي تشفير. لا حاجة إلى مفتاح، ولا يوجد ما يمنع أي شخص من فعل الشيء نفسه بتوكنك:

في الطرفيةbash
echo "$TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d

الفك في متصفحك دون إرسال التوكن

هذه هي الخطوة العملية في هذا الدليل. لاحظ أن الكود التالي لا يفعل أي طلب شبكة: التوكن لا يغادر جهازك ولا يُسجَّل في أي سجل خادم. هذا هو الفرق الجوهري بين أداة تعمل في المتصفح وأداة ترسل ما أدخلته إلى خادم.

فك كامل في المتصفحjavascript
function decodeJwt(token) {
  const part = token.split(".")[1];
  const base64 = part.replace(/-/g, "+").replace(/_/g, "/");
  const padded = base64.padEnd(base64.length + (4 - (base64.length % 4)) % 4, "=");
  return JSON.parse(atob(padded));
}

function inspectJwt(token) {
  const claims = decodeJwt(token);
  if (claims.exp && Date.now() / 1000 > claims.exp) {
    return { valid: false, reason: "منتهي منذ " + new Date(claims.exp * 1000).toISOString(), claims };
  }
  return { valid: true, claims };
}

الخطأ الأكثر شيوعاً: token has expired

مطالبات exp تُقاس بالثواني منذ 1970، لا بالمللي ثانية، وهذا مصدر الخلط الأول:

القيمةالوحدةالنتيجة
exp = 1700000000ثانية Unixطبيعي
exp = 1700000000000مللي ثانيةخطأ — يبدو دائماً منتهياً
exp = "2026-01-01"نصغير صالح

إذا رأيت خطأ انتهاء والـ payload يبدو صحيحاً، فقارن وحدة القياس أولاً. الثانية تُقارن بـ Date.now() مقسوماً على 1000، لا بـ Date.now() مباشرة:

مقارنة صحيحةjavascript
const remaining = claims.exp - Date.now() / 1000;
console.log(remaining > 0 ? `صالح لـ ${Math.floor(remaining / 60)} دقيقة` : "منتهي");

الفك لا يتحقق — التوقيع هو ما يتحقق

هذه هي النقطة التي يجب أن تخرج بها. الأداة التي تفك التوكن تعطيك محتوى الـ payload فقط، ولا تختبر صحته، لأن ذلك يتطلب المفتاح السري الموجود عند الخادم، وهو لا يصل إلى التطبيق الأمامي أبداً. الترتيب الصحيح في تطبيق حقيقي:

  1. الخادم يتحقق من التوقيع بالمفتاح السري عند إصدار التوكن وعند كل طلب.
  2. التطبيق الأمامي يكتفي بقراءة المطالبات لعرض الحالة، دون الوثوق بها.
  3. كل قرار صلاحيات حقيقي يُتخذ على الخادم، لا بناءً على ما في التوكن.

alg none: خلل يمرّ تحت الانتباه

كانت هذه ثغرة في مواصفة JWT الأولى: الخوارزمية none تعني حرفياً لا توقيع. مهاجم يغيّر الخوارزمية إلى none ويحذف التوقيع، فيصبح التوكن بلا حماية. الخطر أن بعض المكتبات قبلت ذلك افتراضياً:

payload تسريب — مثال توضيحيjson
{
  "alg": "none",
  "sub": "1",
  "role": "admin"
}

القاعدة العملية: افرض قائمة الخوارزميات المسموح بها صراحةً في إعدادات التحقق، ولا تثق أبداً بالخوارزمية المعلنة في الـ header، لأنها جزء من البيانات التي يملكها المهاجم.

متى تستخدم أداة فك JWT

  • تشخيص طلبات 401: هل انتهت الصلاحية أم أن التوقيع مرفوض؟
  • قراءة المطالبات لمعرفة الدور دون طلب إضافي للخادم.
  • مقارنة ما يرسله الخادم مع ما يفترضه التطبيق.
  • فحص محتوى توكن في مرحلة التطوير فقط، لا على بيانات إنتاج.