طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 243 · أمن الويب
الدرس 18 · الوحدة 8: تقنية OpenID Connect

التحقق من رموز JWT

الرمز موقّع، فأين الخطر؟ في طريقة التحقق التي تثق بما يقوله المهاجم. ⏱ نحو 45 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تتحقق من JWT بالكامل: توقيع و exp و iss و aud.
  • تشرح ثغرة alg=none وخلط الخوارزميات.
  • تثبّت الخوارزمية المتوقَّعة في التحقق.
  • تختار مكان تخزين الرمز الأأمن.

١التحقق الكامل

JWT يحمي مطالباته بتوقيع، لكن الأمان في كيف تتحقق. التحقق الكامل أربع فحوص، لا التوقيع وحده:

الفحصيمنع
التوقيع صحيحالعبث بالمطالبات (ترقية الرتبة مثلًا)
exp لم ينتهِإعادة استخدام رمز قديم
iss متوقَّعقبول رمز من مُصدِر غريب
aud تطبيقكقبول رمز صدر لتطبيق آخر
const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const jwt = require('jsonwebtoken');const SECRET = 'my-secret'; app.get('/issue', (req, res) =>  res.json({ token: jwt.sign({ sub: 'u1', role: 'user' }, SECRET,    { algorithm: 'HS256', expiresIn: '1h', issuer: 'rafee', audience: 'app', noTimestamp: true }) })); app.post('/check', (req, res) => {  try {    // تحقق كامل: الخوارزمية والمُصدِر والجمهور مثبّتة    const claims = jwt.verify(req.body.token, SECRET,      { algorithms: ['HS256'], issuer: 'rafee', audience: 'app' });    res.json({ sub: claims.sub, role: claims.role });  } catch (e) {    res.status(401).json({ error: 'رمز غير صالح: ' + e.message });  }}); app.listen(3000);

الخادم يصدر رمزًا كامل المطالبات. والتحقق من رمز alg=none مزوّر (رتبة admin) يفشل 401؛ لأننا ثبّتنا الخوارزمية HS256 فلم نقبل none.

٢ثغرة alg=none وخلط الخوارزميات

أخطر ثغرتين في التحقق من JWT سببهما الثقة بما يقوله المهاجم في ترويسة الرمز:

الثغرةالحيلةالدفاع
alg=noneالمهاجم يضع alg:none ويحذف التوقيع، فيقبله متحقق ساذجثبّت الخوارزميات المسموحة؛ لا تقبل none
خلط الخوارزمياترمز موقّع بـ RS256 (مفتاح عام) يُرسل كأنه HS256 بالمفتاح العام سرًّاثبّت الخوارزمية المتوقَّعة فقط

الجذر واحد: لا تدع الرمز يخبرك بخوارزمية التحقق. أنت تحدد الخوارزمية المتوقَّعة، لا ترويسة قد زوّرها المهاجم:

٣أين يُخزَّن الرمز؟

رمز الجلسة/الوصول في الواجهة له مخاطره حسب التخزين:

المكانالخطرالتخفيف
localStorageيقرؤه أي XSSتجنّبه للرموز الحسّاسة
كوكي httpOnlyلا يصله XSSالأفضل، مع SameSite ضد CSRF

وإن طالت صلاحية الرمز، فسرقته تدوم طويلًا. لذلك نقصر عمر رمز الوصول ونجدّده بـ رمز تجديد (refresh token) محفوظ بأمان.

توقّع وتحقّق

التحقق الكامل من JWT يشمل:

ثغرة alg=none سببها:

الدفاع عن خلط الخوارزميات:

أأمن مكان لرمز الجلسة في الواجهة:

الخلاصة

تحدٍّ برمجي: ثبّت خوارزمية التحقق

متقدم

نقطة التحقق تستدعي jwt.verify دون تثبيت الخوارزمية، فهي عرضة لثغرة alg=none. أصلحها بتثبيت HS256، فيُرفض الرمز المزوّر بلا توقيع.

المطلوب:
  • مرّر { algorithms: ['HS256'] } إلى verify.
  • الرمز الصحيح الصادر من /issue يُقبل.
  • رمز alg=none المزوّر (رتبة admin) يُرفض 401.
👀 اعرض الاستجابات المطلوبة
✓ التحقق الآلي: يشغّل المختبر خادمك من جديد ويرسل إليه هذه الطلبات بالترتيب ثم يفحص الردود
  1. GET /issue
  2. POST /check {"token": "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJ1MSIsInJvbGUiOiJhZG1pbiJ9."}
  • الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
  • الإصدار يعطي رمزًا
  • رمز alg=none المزوّر يُرفض ← 401
  • يثبّت الخوارزميات

💡 تلميح 1
أضف خيارًا ثالثًا لـ verify: jwt.verify(token, SECRET, { algorithms: ['HS256'] }).
💡 تلميح 2
بلا تثبيت، قد يقبل المتحقق alg=none بلا توقيع.
💡 تلميح 3
الرمز المزوّر ترويسته alg:none وتوقيعه فارغ.

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const jwt = require('jsonwebtoken');const SECRET = 'my-secret';app.get('/issue', (req, res) =>  res.json({ token: jwt.sign({ sub: 'u1', role: 'user' }, SECRET, { algorithm: 'HS256', noTimestamp: true }) }));app.post('/check', (req, res) => {  try {    const claims = jwt.verify(req.body.token, SECRET, { algorithms: ['HS256'] });    res.json({ sub: claims.sub, role: claims.role });  } catch (e) {    res.status(401).json({ error: 'غير صالح' });  }});app.listen(3000);
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس