في جملة واحدة
توكن ويب JSON (أو JWT) هو طريقة مدمجة وآمنة للاستخدام في الروابط (URL-safe) لتمثيل الادعاءات (claims) اللي تحتاج تُنقل بين طرفين، وعادةً يُستخدم للمصادقة (authentication) والتفويض (authorization) بطريقة يمكن التحقق منها وموثوقة.
المشكلة اللي يحلها
في الأيام الخوالي—خلنا نقول، بداية الألفينات—لما كنت تسجل دخول في موقع، السيرفر كان يسوي لك "جلسة" (session). كانت زي ملف صغير على السيرفر يقول، "المستخدم رقم 123 مسجل دخول وحط دجاجة مطاطية في سلة التسوق حقته". السيرفر كان يعطي متصفحك "كوكي" (cookie) صغير فيه رقم الجلسة، زي تذكرة استلام المعطف. ومع كل طلب ترسله بعد كذا، متصفحك يبرز التذكرة، السيرفر يدور عليها، يلقى ملفك، ويتذكر أنت مين.
هذا النظام كان شغال تمام لسيرفر واحد ضخم (monolithic). لكن بعدين الويب انفجر. صار عندنا خدمات مصغرة (microservices)، تطبيقات الصفحة الواحدة (SPAs)، وتطبيقات جوال كلها تكلم نفس الـ backend. الحين، طلب تسجيل دخولك ممكن يروح للسيرفر A، بس طلبك الجاي عشان تجيب ملفك الشخصي ممكن يروح للسيرفر B. كيف السيرفر B يعرف عن ملف الجلسة اللي موجود على السيرفر A؟
كان ممكن تجبر المستخدم إنه يكلم نفس السيرفر دايمًا ("الجلسات اللاصقة" أو sticky sessions)، بس هذا يعتبر عنق زجاجة. وكان ممكن تسوي قاعدة بيانات مركزية للجلسات (زي Redis) تشاركها كل السيرفرات، بس هذي قطعة بنية تحتية زيادة لازم تديرها ونقطة فشل إضافية.
المشكلة الأساسية هي statefulness (الحالة التخزينية). السيرفر لازم "يتذكرك".
الـ JWTs (تُنطق "جوتس") قلبت هذي الفكرة رأسًا على عقب. ماذا لو المستخدم يقدر يشيل إثبات هويته معاه، زي جواز السفر؟ التوكن نفسه بيحتوي على كل المعلومات اللي يحتاجها السيرفر: مين المستخدم، إيش مسموح له يسوي، ومتى تنتهي صلاحية وصوله. السيرفر ما يحتاج يتذكر أي شيء بين الطلبات. هذي هي المصادقة stateless (عديمة الحالة)، وهي مفتاح بناء أنظمة قابلة للتوسع وموزعة. السيرفر بس يحتاج يتأكد إذا جواز السفر (الـ JWT) صالح ومو مزور.
كيف يشتغل من تحت لتحت
الـ JWT ما هو كتلة عشوائية من الخرابيط. هو عبارة عن سلسلة نصية (string) منظمة جدًا ومكونة من ثلاثة أجزاء، تفصل بينها نقاط (.).
xxxxx.yyyyy.zzzzz
خلونا نفصص كل جزء.
الـ Header (بطاقة "النوع")
الجزء الأول هو الـ header. هو عبارة عن كائن JSON بسيط يحتوي على بيانات وصفية (metadata) عن التوكن نفسه، وأهمها خوارزمية التوقيع المستخدمة ونوع التوكن.
{
"alg": "HS256",
"typ": "JWT"
}
alg: خوارزمية التوقيع.HS256تعني إن هذا التوكن موقّع باستخدام HMAC-SHA256، وهي خوارزمية متماثلة (symmetric) (بنتكلم عنها بعد شوي). من الخيارات الشائعة الثانيةRS256(اللي تستخدم زوج من مفاتيح RSA عام/خاص).typ: نوع التوكن. بالنسبة للـ JWTs، هو ببساطة "JWT".
بعدين هذا الـ JSON يتم ترميزه بـ Base64Url عشان ينتج الجزء الأول من التوكن. Base64 هو نظام ترميز، وليس تشفيرًا. هو بس يحول البيانات الثنائية إلى سلسلة نصية آمنة للنقل عبر الويب. فكر فيها كأنك تكتب "هذه بطاقة بريدية" على ظهر بطاقة بريدية—أي شخص يعترضها يقدر يقرأها.
الـ Payload (قسم "الادعاءات")
الجزء الثاني هو الـ payload. هذا هو الجزء الدسم. هو كائن JSON ثاني يحتوي على "الادعاءات" (claims)، وهي عبارات عن المستخدم ("الموضوع" أو subject) وبيانات مفيدة ثانية.
{
"sub": "10987-23456-98765",
"name": "Grace Hopper",
"admin": true,
"iat": 1516239022,
"exp": 1516242622
}
الـ claims تجي بثلاث نكهات:
- Registered Claims (ادعاءات مسجلة): هذي مجموعة من الادعاءات المحددة مسبقًا والموصى بها عشان توفر قابلية التشغيل البيني (interoperability). هي مو إجبارية، بس مفيدة جدًا.
| Claim | الاسم | الوصف |
|---|---|---|
iss |
Issuer (المُصدر) | مين اللي أصدر التوكن (مثلاً، https://api.mycoolsite.com). |
sub |
Subject (الموضوع) | المستخدم أو الكيان اللي يخصه التوكن (مثلاً، user ID). |
aud |
Audience (الجمهور) | لمين موجه هذا التوكن (مثلاً، https://api.mycoolsite.com). |
exp |
Expiration Time (وقت الانتهاء) | متى تنتهي صلاحية التوكن. طابع زمني Unix رقمي (عدد الثواني من بداية عصر يونكس). |
iat |
Issued At (وقت الإصدار) | متى تم إصدار التوكن. برضه طابع زمني Unix. |
- Public Claims (ادعاءات عامة): هذي ادعاءات مخصصة أنت تسويها، بس عشان تتجنب تضارب الأسماء، المفروض تكون معرفة في سجل IANA JSON Web Token Claims أو تكون URI يحتوي على مساحة اسم مقاومة للتصادم.
- Private Claims (ادعاءات خاصة): هذي هي أكثر الادعاءات المخصصة شيوعًا، تُنشأ لمشاركة المعلومات بين الأطراف اللي تتفق على استخدامها (زي
admin: trueفي مثالنا). هنا تحط بياناتك الخاصة بالتطبيق.
تمامًا زي الـ header، كائن الـ payload JSON بالكامل يتم ترميزه بـ Base64Url عشان يشكل الجزء الثاني من الـ JWT. مرة ثانية، هذا ليس تشفيرًا. أبدًا لا تحط معلومات حساسة زي كلمات المرور في الـ payload.
الـ Signature (الختم المانع للتلاعب)
هذا هو الجزء اللي يوفر الأمان. التوقيع (signature) يُستخدم للتحقق من أن مُرسل الـ JWT هو نفسه اللي يدعيه، ولضمان أن الرسالة ما تغيرت في الطريق.
يتم إنشاؤه عن طريق أخذ الـ header المرمّز، والـ payload المرمّز، ومفتاح سري (secret key)، وتشغيلهم على الخوارزمية المحددة في الـ header. في مثالنا اللي يستخدم HS256، العملية تبدو كذا:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
الزبدة: يتم استخدام secret (مفتاح سري) ما يعرفه إلا السيرفر. لما السيرفر يستقبل JWT، يعيد تشغيل نفس الحسبة هذي بالضبط بالـ header والـ payload اللي وصلوه. إذا التوقيع اللي ينتجه طابق التوقيع اللي على التوكن، السيرفر يعرف شيئين:
- الأصالة (Authenticity): التوكن تم إنشاؤه من شخص يعرف المفتاح السري (يعني، السيرفر نفسه).
- السلامة (Integrity): الـ header والـ payload ما تم التلاعب فيهم. لو مهاجم غيّر
"admin": falseإلى"admin": trueفي الـ payload، التوقيع ما عاد بيطابق.
هذا التوقيع هو الختم الهولوغرافي المانع للتزوير على جواز سفرنا.
قصص من الواقع
متاهة الـ Microservices
شركة تجارة إلكترونية سريعة النمو، اسمها "ScaleFast"، قررت تفكك الـ backend الضخم حقها (monolithic) إلى أسطول من الخدمات المصغرة (microservices): وحدة للمستخدمين، وحدة للطلبات، وحدة للمخزون، إلخ. النظام القديم كان يستخدم جلسات (sessions) على السيرفر. بس في العالم الجديد، كيف خدمة الطلبات OrderService تعرف إن الطلب فعلًا جاي من مستخدم مسجل دخول، بدون ما تحتاج تكلم خدمة المستخدمين UserService مع كل طلب؟ هذا بيكون بطيء ويهدم الهدف من الفصل بين الخدمات.
الحل كان JWT. لما المستخدم يسجل دخول، خدمة المصادقة AuthService الجديدة تصدر JWT يحتوي على userId وصلاحياته roles. متصفح المستخدم يرفق هذا الـ JWT في هيدر Authorization مع كل طلب يرسله للخدمات المصغرة الثانية. خدمة الطلبات OrderService وخدمة المخزون InventoryService ما يحتاجون يكلمون AuthService؛ كل اللي يحتاجونه هو معرفة المفتاح السري المشترك. يقدرون يتحققون من توقيع الـ JWT بشكل مستقل، ويثقون في الـ userId اللي داخله، ويعالجون الطلب.
الدرس المستفاد: الـ JWTs هي لغة التواصل المشتركة (lingua franca) في مصادقة الـ microservices، وتمكن الخدمات من أن تكون stateless وقابلة للتحقق بشكل مستقل.
ملحمة تطبيقات الصفحة الواحدة (SPA)
مطور اسمه أليكس كان يبني لوحة تحكم أنيقة بـ React. الواجهة الأمامية (frontend) كانت تطبيق صفحة واحدة (SPA) مستضافة على سيرفر ثابت (static host)، وكانت تكلم API منفصل في الـ backend. أليكس كان يعاني مع المصادقة القديمة المعتمدة على الكوكيز، ووقع في كابوس مشاكل مشاركة الموارد عبر المصادر المختلفة (CORS) لأن الواجهة الأمامية والـ backend كانوا على دومينات مختلفة.
الفريق تحول إلى JWT. الآن، بعد ما المستخدم يسجل دخول باسم المستخدم وكلمة المرور، الـ API يرجع JWT. تطبيق React حق أليكس يخزن هذا التوكن في الذاكرة ويرفقه مع كل استدعاء للـ API: Authorization: Bearer <the-jwt>. الـ API backend صار stateless؛ هو بس يتأكد من الـ bearer token مع كل طلب وارد. وداعًا لصداع الكوكيز مع CORS.
الدرس المستفاد: الـ JWTs توفر إثبات هوية نظيف ومحمول يشتغل بشكل رائع لفصل تطبيقات الواجهة الأمامية الحديثة عن الـ backend APIs.
أخطاء ومصائد شائعة
- وضع بيانات حساسة في الـ payload. وقف! الـ payload مرمّز بـ Base64Url، وهذا الترميز سهل جدًا عكسه. هو ليس مشفرًا. أي شخص يحصل على التوكن يقدر يقرأ الـ payload. عامله كبطاقة بريدية، مو رسالة مختومة.
- نسيان التحقق من التوقيع. إيش فايدة الميزات الأمنية في جواز السفر إذا موظف الجوازات ما يشيك عليها؟ مجرد فك ترميز الـ payload والثقة بمحتوياته بدون التحقق من التوقيع يعتبر ثغرة أمنية كارثية. المهاجم يقدر يزور أي payload يبغاه.
- الثقة العمياء بهيدر
alg. ثغرة مشهورة في الماضي كانت تتضمن أن المهاجمين ينشئون توكن ويغيرون الهيدر إلى{"alg": "none"}. بعض المكتبات البرمجية اللي إعداداتها غلط كانت تشوف "none" و"تتحقق" من التوقيع عن طريق، حسنًا، عدم فعل أي شيء، وبالتالي تقبل التوكن المزور على أنه صالح. دايمًا خل سيرفرك يفرض استخدام خوارزمية محددة ومتوقعة (مثلاً،HS256). - تسريب مفتاحك السري المتماثل (symmetric secret key). بالنسبة لخوارزميات HMAC زي
HS256، المفتاح السري هو مفاتيح المملكة. إذا تسرب، أي شخص يقدر يزور توكنز لأي مستخدم بأي صلاحيات. حافظ عليه زي ما تحافظ على كلمة مرورك. - عدم تحديد تاريخ انتهاء (
exp). التوكن اللي يعيش للأبد هو مسؤولية ضخمة. إذا تم اختراقه في أي وقت، المهاجم يقدر يستخدمه إلى ما لا نهاية. دايمًا حدد وقت انتهاء قصير بشكل معقول واستخدم آلية refresh token للجلسات طويلة الأمد.
ليش لازم يكون على رادارك
لازم تفكر بـ "JWT" كل ما كنت تتعامل مع المصادقة (authentication) أو التفويض (authorization) في بيئة موزعة.
- قاعد تبني API لتطبيق صفحة واحدة (SPA) أو تطبيق جوال.
- قاعد تصمم بنية microservices حيث الخدمات تحتاج تثق في الطلبات اللي جاية من بعضها.
- تحتاج مصادقة stateless تقدر تتوسع أفقيًا بدون مخزن جلسات مشترك.
- قاعد تنفذ عمليات تفويض لمرة واحدة، زي روابط إعادة تعيين كلمة المرور أو تفعيل البريد الإلكتروني، حيث التوكن المستقل واللي له تاريخ انتهاء يعتبر حل مثالي.
إنه المعيار الحديث لتمثيل الادعاءات (claims) بأمان، وفهم نقاط قوته—وضعفه—شيء لا يمكن التفاوض عليه للمطور اليوم.
تعمق أكثر
- RFC 7519: المواصفات الرسمية لـ JSON Web Token (JWT). مصدر الحقيقة.
- jwt.io: مصدر رائع مع مصحح أخطاء مباشر وقائمة بالمكتبات البرمجية لكل لغة تقريبًا.
- OWASP JWT Cheat Sheet: دليل أساسي لأفضل الممارسات الأمنية والمزالق في استخدام JWTs (النصائح تنطبق على كل اللغات).
- MDN Web Docs: Authorization header: تعلم عن نظام المصادقة
Bearerالشائع استخدامه لنقل JWTs. - Wikipedia: JSON Web Token: نظرة عامة ممتازة للمفهوم وتاريخه.