ما هو JWT؟ شرح رموز JSON Web Token
رمز JSON Web Token (اختصاراً JWT) سلسلة نصية مضغوطة تحمل مجموعة من المطالبات (claims) — عبارات مثل «هذا هو المستخدم 42، بدور مدير، صالح حتى الساعة 15:00» — إلى جانب توقيع تشفيري يتيح للمستقبل التأكد من أن المطالبات صادرة عن جهة يثق بها ولم تُعدَّل. رموز JWT هي الصيغة القياسية لمصادقة واجهات API ورموز وصول OAuth 2.0 ورموز هوية OpenID Connect.
يشرح هذا الدليل ما بداخل الرمز، ومعنى المطالبات القياسية، وكيفية فك ترميزه والتحقق منه، وقواعد الأمان التي تفصل بين تطبيق آمن واختراق.
الأجزاء الثلاثة لرمز JWT
يتكون JWT من ثلاثة مقاطع مرمّزة بـ Base64URL تفصل بينها نقاط: header.payload.signature. وBase64URL هو Base64 بأحرف آمنة لعناوين URL ودون حشو، حتى يمكن للرمز الانتقال في الترويسات وسلاسل الاستعلام وملفات تعريف الارتباط.
- الترويسة (Header) — JSON يصف نوع الرمز وخوارزمية التوقيع، مثل {"alg": "HS256", "typ": "JWT"}. وقد تحمل أيضاً kid (معرّف المفتاح) الذي يخبر المتحقق بالمفتاح الواجب استخدامه.
- الحمولة (Payload) — المطالبات ككائن JSON. يمكن وضع أي شيء هنا، لكنه مرمّز فقط لا مشفّر: كل من يملك الرمز يستطيع قراءته.
- التوقيع (Signature) — يُحسب على الترويسة والحمولة المرمّزتين بالخوارزمية المذكورة في الترويسة. تستخدم HS256 سراً مشتركاً (HMAC)؛ أما RS256 وES256 فتستخدمان مفتاحاً خاصاً للتوقيع ومفتاحاً عاماً للتحقق.
ثلاثة مقاطع مرمّزة بـ Base64URL تفصل بينها نقاط: header.payload.signature
الجزء 1:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 .الجزء 2:eyJzdWIiOiI0MiIsIm5hbWUiOiJMYXlsYSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc1NjcwMDAwMCwiZXhwIjoxNzU2NzAzNjAwfQ .الجزء 3:4l3Y2cYpXqZ0cVZ5m4Q8n1fGx0y8s3oT0kq4rV7cWvA
- 1–2مقروءان لأي شخص: يُفك ترميزهما إلى JSON دون أي مفتاح. مرمّزان فقط لا مشفّران.
- 1–2مُدخل التوقيع: يُحسب التوقيع على هذين المقطعين المرمّزين، فتغيير أيّ منهما يُفشل التحقق.
- 3يحتاج التحقق منه إلى المفتاح: السر المشترك لـ HS256، والمفتاح العام لـ RS256 وES256.
- 1الترويسة (Header)نوع الرمز وخوارزمية التوقيع؛ وقد تحمل أيضاً
kid(معرّف المفتاح).{"alg":"HS256","typ":"JWT"}ثبّت الخوارزمية في الخادم؛ لا تدع هذه الترويسة تقررها - 2الحمولة (Payload)مطالبات قياسية:
sub(الموضوع) وiat(وقت الإصدار) وexp(انتهاء الصلاحية)؛ وnameوroleمطالبات مخصصة.{"sub":"42","name":"Layla","role":"admin","iat":1756700000,"exp":1756703600}صالح لساعة واحدة:exp − iat = 3600ثانية - 3التوقيع (Signature)يُحسب على الترويسة والحمولة المرمّزتين بالخوارزمية المذكورة في الترويسة: تستخدم
HS256سراً مشتركاً (HMAC).HMAC-SHA256(secret, "eyJhbGci…" + "." + "eyJzdWIi…")
لا تذكر الصفحة سراً لهذا المثال، لذا لا يمكن التحقق من توقيعه؛ فهو يوضّح موضع التوقيع فقط.
النسخة النصية
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiI0MiIsIm5hbWUiOiJMYXlsYSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc1NjcwMDAwMCwiZXhwIjoxNzU2NzAzNjAwfQ
.4l3Y2cYpXqZ0cVZ5m4Q8n1fGx0y8s3oT0kq4rV7cWvA
header → {"alg":"HS256","typ":"JWT"}
payload → {"sub":"42","name":"Layla","role":"admin","iat":1756700000,"exp":1756703600}المطالبات القياسية ومعانيها
- iss (المُصدر) — من أنشأ الرمز، وعادةً عنوان خادم المصادقة.
- sub (الموضوع) — من يتحدث عنه الرمز، وغالباً معرّف المستخدم.
- aud (الجمهور) — الخدمة التي يُقصد بها الرمز. يجب على المتحقق رفض الرموز الموجهة لجمهور آخر.
- exp (انتهاء الصلاحية) — طابع Unix زمني يصبح الرمز بعده غير صالح. الأعمار القصيرة (دقائق لرموز الوصول) تحدّ من ضرر التسريب.
- nbf (ليس قبل) وiat (وقت الإصدار) — طوابع زمنية تحدد متى أصبح الرمز صالحاً ومتى أُنشئ.
- jti (معرّف JWT) — معرّف فريد يُستخدم لاكتشاف إعادة الاستخدام أو لإبطال رمز بعينه.
- المطالبات المخصصة — الأدوار والصلاحيات ومعرّف المستأجر والبريد الإلكتروني. أبقها صغيرة: يُرسل الرمز مع كل طلب.
فك الترميز ليس تحققاً
فك ترميز JWT يعني فك ترميز Base64URL للترويسة والحمولة لقراءتهما. يستطيع أي شخص فعل ذلك دون أي مفتاح — وهذا بالضبط سبب وجوب عدم الوثوق بحمولة مفكوكة الترميز بمفردها. أما التحقق فيعني إعادة حساب التوقيع بالمفتاح والخوارزمية الصحيحين والتأكد من تطابقه، ثم فحص exp وnbf وiss وaud.
أداة فك الترميز أونلاين هي الأداة المناسبة لقراءة رمز أثناء تصحيح الأخطاء: معرفة المستخدم الذي يسمّيه، والأدوار التي يحملها، وموعد انتهاء صلاحيته. لكنها ليست فحصاً أمنياً، والأداة التي تعمل داخل متصفحك هي النوع الوحيد الذي يجوز لصق رمز حقيقي فيه.
HS256 مقابل RS256 مقابل ES256
- HS256 (HMAC مع SHA-256) — سر مشترك واحد يوقّع ويتحقق. بسيط وسريع ومناسب عندما تصدر خدمة واحدة الرموز وتستهلكها. كل طرف يستطيع التحقق يستطيع أيضاً التزوير.
- RS256 (توقيع RSA) — مفتاح خاص يوقّع ومفتاح عام يتحقق. تستطيع أي خدمة التحقق بالمفتاح العام المنشور (غالباً عبر نقطة نهاية JWKS) دون القدرة على إنشاء رموز. وهو الافتراضي لدى مزوّدي OAuth.
- ES256 (ECDSA مع P-256) — نموذج غير متماثل كـ RS256 بمفاتيح وتوقيعات أصغر بكثير.
- none — خوارزمية تعني «بلا توقيع». يجب على المتحققين رفضها؛ فقبول alg=none إحدى الثغرات الكلاسيكية في JWT.
قواعد أمان JWT
- تحقق من التوقيع وثبّت الخوارزمية في الخادم. لا تدع ترويسة الرمز نفسها تقرر الخوارزمية المستخدمة.
- افحص exp دائماً، وقارن aud وiss بقيمك الخاصة.
- لا تضع أسراراً أو كلمات مرور أو بيانات شخصية حساسة في الحمولة — فهي مقروءة لكل من يحمل الرمز. استخدم JWE إن احتجت إلى التشفير.
- أبقِ رموز الوصول قصيرة العمر واستخدم رمز تحديث للحصول على رموز جديدة.
- خزّن الرموز بحذر في المتصفحات: ملف تعريف ارتباط HttpOnly وSecure وSameSite أكثر أماناً من localStorage الذي يستطيع أي سكربت محقون قراءته.
- استخدم سراً قوياً لـ HS256 — 256 بت من العشوائية على الأقل، ولا تستخدم كلمة مرور أبداً.
- خطط للإبطال: يبقى JWT صالحاً حتى انتهاء صلاحيته، لذا يحتاج تسجيل الخروج أو الحساب المخترق إلى قائمة رفض (jti) أو أعمار قصيرة.
جرّبها: مولّد الأسرار العشوائية جرّبها: مفكّك ترميز رموز OAuth
JWT في OAuth 2.0 وOpenID Connect
غالباً ما تكون رموز وصول OAuth 2.0 من نوع JWT، وإن كانت المواصفة لا تشترط ذلك؛ أما رموز هوية OpenID Connect فهي كذلك دائماً. يصف رمز الهوية المستخدم المصادَق عليه (sub والبريد والاسم) لتطبيق العميل؛ بينما يمنح رمز الوصول العميل إذن استدعاء واجهة API (النطاق وaud). يُتحقق منهما بالطريقة نفسها، لكن يجب ألا يُرسل رمز الهوية إلى واجهة API كما لو كان رمز وصول.
الأسئلة الشائعة
هل JWT مشفّر؟
لا — رمز JWT الموقّع القياسي (JWS) مرمّز بـ Base64URL فقط. حمولته مقروءة لكل من يملك الرمز. توجد رموز مشفّرة (JWE) لكنها أقل شيوعاً بكثير.
هل يمكن فك ترميز JWT دون السر؟
نعم. الترويسة والحمولة مجرد Base64URL، فأي أداة فك ترميز تستطيع قراءتهما. لا يلزم السر أو المفتاح إلا للتحقق من التوقيع، وهو ما يثبت أصالة الرمز.
كيف أتحقق من انتهاء صلاحية JWT؟
فكّ ترميزه وقارن مطالبة exp (طابع Unix زمني بالثواني) بالوقت الحالي. تفعل مكتبات التحقق ذلك تلقائياً؛ وتعرض أداة فك الترميز أونلاين موعد الانتهاء كتاريخ مقروء.
ما الفرق بين JWT وملف تعريف ارتباط الجلسة؟
ملف تعريف ارتباط الجلسة معرّف معتم يبحث عنه الخادم في مخزنه؛ أما JWT فيحمل بيانات الجلسة نفسها موقّعة، فلا يحتاج الخادم إلى بحث. تتوسع رموز JWT عبر الخدمات بسهولة أكبر؛ بينما الجلسات أسهل في الإبطال.
أين أخزّن JWT في تطبيق ويب؟
يُفضَّل ملف تعريف ارتباط HttpOnly وSecure مع SameSite، حتى لا يستطيع JavaScript في الصفحة قراءته. localStorage مريح لكنه يعرّض الرمز لأي ثغرة برمجة عبر المواقع.
هل من الآمن لصق JWT في أداة فك ترميز أونلاين؟
فقط إذا كانت الأداة تعمل بالكامل في متصفحك ولا ترسل الرمز إلى أي مكان. تعمل أداة فك ترميز JWT في متقن محلياً؛ ومع ذلك عامل رموز الإنتاج كأسرار وفضّل رموز الاختبار متى استطعت.
المراجع
- RFC 7519: JSON Web Token (JWT)يعرّف صيغة JWT — الترويسة والحمولة والتوقيع — والمطالبات المسجّلة iss وsub وaud وexp وnbf وiat وjti.
- RFC 7515: JSON Web Signature (JWS)كيف يُوقَّع JWT ويُسلسَل: الترويسة المحمية ومدخل التوقيع والتسلسل المضغوط.
- RFC 8725: JSON Web Token Best Current Practicesالقواعد الأمنية لتطبيقات JWT: رفض alg=none وتثبيت الخوارزميات والتحقق من iss/aud واستخدام أعمار قصيرة.
- OWASP Cheat Sheet Series: JSON Web Tokenتحصين JWT عملياً: التباس الخوارزمية، وسرقة الرمز، والتخزين في المتصفح، والإبطال.
تمرين عملي
راجع رمز الوصول في تطبيق مصرفي واختبارات التحقق منه
أنت تختبر تطبيق الويب للخدمات المصرفية عبر الإنترنت لدى مصرف (مثال خيالي)، وواجهة الحسابات (Accounts API) التي يستدعيها، والبناء 4.2.0 منشور على بيئة Staging. تحتوي المادة أدناه على مواصفة الرمز لدى الفريق، وهي أساس الاختبار (Test basis) في البنود T1–T7؛ وعلى رمز وصول نسخته من المتصفح بعد تسجيل الدخول باسم عميل تجريبي؛ وعلى نتائج ثمانية اختبارات تحقق نفّذها زميل على واجهة الحسابات؛ وعلى سؤال من مطوّر الواجهة الأمامية (Front-end).
فكّ ترميز الرمز، وافحصه مقابل المواصفة، واحكم على نتيجة كل اختبار، ثم أجب المطوّر. خصّص نحو 30 دقيقة. تعرض أداة JWT Decoder الترويسة والحمولة، وتعرض قيم iat وnbf وexp تواريخَ بتوقيت UTC (انسخ أسطر الرمز السبعة فقط، فالأداة تتجاهل فواصل الأسطر). وبعد أن تكوّن رأيك بنفسك، تنبّهك أداتا JWT Claims Analyzer وJWT Header Analyzer إلى المطالبات وقيم الترويسة الخطرة؛ لكنهما تحكمان وفق الممارسات الشائعة لا وفق T1–T7، فقد تقبلان ما تمنعه المواصفة. وإن كنت تعمل بالورقة والقلم، فالرمز مفكوك الترميز في الفقرة الأولى من الإجابة النموذجية.
تعديل الرمز كما في V2 وV3 اختبار لا تجريه إلا على أنظمة فريقك، أو بإذن مكتوب من مالك النظام.
الخدمات المصرفية عبر الإنترنت لمصرف (مثال خيالي) - تطبيق الويب وواجهة الحسابات
البناء 4.2.0 على بيئة Staging. واجهة الحسابات Accounts API:
https://api.staging.bank.example/v1/accounts
مواصفة الرمز (أساس الاختبار)
T1 يوقّع خادم المصادقة رموز الوصول بـ ES256. وتقبل واجهة الحسابات ES256 فقط،
وتأخذ المفتاح العام من JWKS خادم المصادقة، وتختاره بحسب kid.
T2 يجب أن تكون iss هي https://auth.staging.bank.example وأن تكون aud هي accounts-api.
T3 عمر رمز الوصول 10 دقائق (exp = iat + 600)، ويجدّده تطبيق الويب برمز تحديث.
T4 لا يحمل رمز الوصول إلا iss وsub (رقم العميل) وaud وiat وnbf وexp وjti وscope.
لا بيانات شخصية.
T5 يضع تسجيل الخروج قيمة jti الخاصة بالرمز في قائمة رفض تفحصها الواجهة.
T6 يحفظ تطبيق الويب رمز الوصول في ملف تعريف ارتباط HttpOnly وSecure وSameSite،
ولا يحفظه أبداً في localStorage.
T7 تجيب الواجهة بـ 401 عن كل رمز يفشل التحقق منه.
رمز وصول العميل التجريبي C-100482، نُسخ يوم 30 سبتمبر 2026 بعد تسجيل الدخول مباشرة،
من localStorage في المتصفح (المفتاح: access_token):
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InN0Zy0yMDI2LTA5In0.eyJpc3MiOiJodHRwczov
L2F1dGguc3RhZ2luZy5iYW5rLmV4YW1wbGUiLCJzdWIiOiJDLTEwMDQ4MiIsImF1ZCI6ImFjY291bnRzLWFw
aSIsImlhdCI6MTc5MDc0ODAwMCwibmJmIjoxNzkwNzQ4MDAwLCJleHAiOjE3OTA4MzQ0MDAsImp0aSI6Ijdk
MmYwYzllLTRiMWEtNGU4ZS05YTNkLTVjNmIyZjFlOGE0MCIsInNjb3BlIjoiYWNjb3VudHM6cmVhZCB0cmFu
c2ZlcnM6d3JpdGUiLCJyb2xlIjoiY3VzdG9tZXIiLCJuYW1lIjoiU2FyYSBBbC1IYXJiaSIsIm5hdGlvbmFs
X2lkIjoiMTIzNDU2Nzg5MCJ9._rh027IPcSrasUBWj1c2UiLI8IbEc7oMm7nIwMXaSc7Mt0yf3TWbGdoASKU
OItR_TDURrtVHr0XfGXNLMb9u-A
اختبارات التحقق على GET /v1/accounts، نفّذها زميل على بيئة Staging
V1 الرمز المنسوخ دون تغيير، ضمن مدة صلاحيته
-> 200 OK حسابات C-100482
V2 عُدّلت الحمولة: غُيّرت sub إلى C-100483، وبقيت الترويسة والتوقيع دون تغيير
-> 401 Unauthorized
V3 غُيّرت الترويسة إلى {"alg":"none","typ":"JWT"}، وغُيّرت sub إلى C-100483،
وحُذف التوقيع (ينتهي الرمز بنقطة)
-> 200 OK حسابات C-100483
V4 رمز انقضى وقت exp فيه قبل 40 ثانية
-> 200 OK حسابات C-100482
V5 رمز انقضى وقت exp فيه قبل 15 دقيقة
-> 401 Unauthorized
V6 رمز وصول أصدره خادم المصادقة لواجهة البطاقات Cards API (aud: cards-api)
-> 200 OK حسابات C-100482
V7 رمز الهوية OpenID Connect الخاص بتطبيق الويب (aud: web-banking-app؛
يحمل name وemail، ولا يحمل scope)
-> 200 OK حسابات C-100482
V8 الرمز المنسوخ، أُرسل مجدداً بعد أن سجّل العميل خروجه
-> 401 Unauthorized
سؤال من مطوّر الواجهة الأمامية (Front-end)
يعرض رأس الصفحة اسم العميل، ونخفي قائمة «التحويلات» حين لا تحتوي scope على
transfers:write. ونقرأ الاثنين من رمز الوصول بـ JavaScript.
هل يمكن أن يبقى name وnational_id في الرمز؟- فكّ ترميز الرمز ودوّن
iatوnbfوexpتواريخَ بتوقيت UTC. وابتداءً من الساعة 06:00 UTC يوم 1 أكتوبر 2026 تعرض الأداة الرمز منتهي الصلاحية؛ فاحكم على عمره لا على حالته اليوم. - افحص الترويسة وكل مطالبة والمكان الذي حُفظ فيه الرمز مقابل T1–T7. وصنّف كل بند: صحيح، أو عيب، أو سؤال للفريق، واذكر القاعدة التي يخالفها.
- اكتب لكل صف من V1–V8 النتيجة المتوقعة من المواصفة قبل أن تقرأ النتيجة الفعلية، ثم صنّف الصف: صحيح، أو عيب، أو سؤال، واجمع العيوب التي يجمعها سبب جذري واحد.
- اذكر ثلاثة اختبارات تحقق على الأقل لا يتضمنها السجل بعد.
- أجب عن سؤال المطوّر.
- اختر الملاحظة التي ستبلغ عنها أولاً، واكتب عنوان تقرير العيب الخاص بها، واذكر سبب أولويتها.
إظهار الإجابة النموذجية
الرمز بعد فك ترميزه. الترويسة: {"alg":"ES256","typ":"JWT","kid":"stg-2026-09"}. الحمولة: iss هي https://auth.staging.bank.example، وsub هي C-100482، وaud هي accounts-api، وiat وnbf كلتاهما 1790748000 (30 سبتمبر 2026، الساعة 06:00:00 UTC)، وexp هي 1790834400 (1 أكتوبر 2026، الساعة 06:00:00 UTC)، وjti هي 7d2f0c9e-4b1a-4e8e-9a3d-5c6b2f1e8a40، وscope هي accounts:read transfers:write، وrole هي customer، وname هي Sara Al-Harbi، وnational_id هي 1234567890. ومقطع التوقيع 86 حرفاً، أي 64 بايتاً، وهو حجم توقيع ES256.
الخلاصة: خمسة عيوب (عمر الرمز، والبيانات الشخصية، ومكان الحفظ، وخوارزمية none في V3، وعدم فحص الجمهور في V6 وV7)، وثلاثة أسئلة للفريق (role، وهامش السماح في V4، وهل تفحص الواجهة scope، وهو سؤال يثيره V7)، وأربعة صفوف تتصرف فيها الواجهة تصرفاً صحيحاً (V1 وV2 وV5 وV8). ولم يحتج فك الترميز إلى أي مفتاح، وهذه هي الفكرة المحورية في الدليل: كل ما في الحمولة يستطيع قراءته كل من يحمل الرمز.
الترويسة وiss وaud وsub وjti وscope — صحيحة. فقيمة alg هي ES256 ومعها kid كما يشترط T1، وiss وaud مطابقتان لـ T2. وتساوي nbf مع iat أمر طبيعي: الرمز صالح منذ لحظة إصداره. وjti هي ما يجعل قائمة الرفض في T5 ممكنة.
عمر الرمز — عيب (T3). قيمة exp - iat هي 86400 ثانية، أي 24 ساعة بدلاً من 600 ثانية (10 دقائق)، فالرمز المسرَّب يمكن استخدامه يوماً كاملاً بدلاً من دقائق معدودة. وقد تعدّ أداة عامة لتحليل المطالبات عمراً يبلغ 24 ساعة مقبولاً، لكن أساس الاختبار هنا هو T3، وT3 يحدد 10 دقائق. الخطورة: رئيسي (Major). النتيجة المتوقعة: exp = iat + 600. وبعد الإصلاح تحقّق أيضاً من أن الرمز يُرفض بعد دقائقه العشر بقليل (مضافاً إليها هامش السماح من V4)، ومن أن تطبيق الويب يجدّده برمز التحديث دون أن يُخرج العميل من حسابه.
name وnational_id — عيب (T4). الحمولة مرمّزة لا مشفّرة: لقد قرأت للتو رقم هوية وطنية دون أي مفتاح، ويستطيع ذلك كل من يرى الرمز في تخزين المتصفح أو في سجل أو في وكيل (Proxy) أو في لقطة شاشة مرفقة بتقرير عيب. كما أن الرمز يُرسل مع كل طلب. الخطورة: رئيسي، وقد تجعلها قواعد حماية البيانات في المصرف أعلى. النتيجة المتوقعة: لا تحمل الحمولة إلا المطالبات التي يسردها T4. وهذه القاعدة تخص رمز الوصول؛ أما رمز الهوية فموجّه إلى تطبيق الويب ويجوز أن يحمل الاسم (انظر سؤال المطوّر).
role — سؤال للفريق. لا يذكرها T4، فإما أن المواصفة قديمة وإما أن المطالبة يجب أن تُحذف. والدور داخل رمز موقّع ليس مشكلة أمنية في ذاته لأن التوقيع يحميه، لكن بشرط أن تتحقق الواجهة من التوقيع بخوارزمية مثبّتة، ويُظهر V3 أنها لا تفعل ذلك اليوم: فمع none يستطيع أي شخص أن يكتب أي دور.
مكان الحفظ — عيب (T6). الرمز محفوظ في localStorage تحت المفتاح access_token، حيث يستطيع قراءته أي سكربت يعمل في الصفحة، بما في ذلك سكربت محقون عبر ثغرة برمجة عبر المواقع (XSS). ومع عمر يبلغ 24 ساعة يبقى الرمز المسروق صالحاً يوماً كاملاً ما لم يسجّل العميل خروجه (ويُظهر V8 أن تسجيل الخروج يبطله فعلاً). الخطورة: رئيسي. النتيجة المتوقعة: ملف تعريف ارتباط HttpOnly وSecure وSameSite. وحين يصل الإصلاح، الصق ترويسة Set-Cookie الخاصة به في أداة Cookie Analyzer لتتأكد من السمات الثلاث، وتحقّق من أن localStorage لم يعد يحتوي على الرمز.
V1 — صحيح: 200 مع حسابات العميل نفسه. V2 — صحيح: 401. فتعديل الحمولة يكسر التوقيع، والواجهة تلاحظ ذلك. وهذا هو الفرق بين فك الترميز والتحقق: يستطيع أي شخص أن يعدّل حمولة مفكوكة الترميز، لكن لا يُنتج توقيعاً مطابقاً لها إلا المفتاح الخاص لخادم المصادقة.
V3 — عيب حرج (Critical) (T1 وT7). النتيجة المتوقعة: 401، لأن T1 لا يسمح إلا بـ ES256. والاستجابة 200 مع حسابات العميل C-100483 هي الإخفاق (Failure)، أما العيب الذي خلفه فهو أن الواجهة تدع ترويسة الرمز نفسها تختار الخوارزمية، ومع none لا تفحص أي توقيع إطلاقاً. ومع V2 يتضح أن التحقق يعمل حين تقول الترويسة ES256، ويتوقف حين تطلب الترويسة ذلك. فكل من يعرف رقم عميل أو يخمّنه يستطيع قراءة حساباته، ويستطيع أن يكتب أي مطالبة أخرى أيضاً، مثل scope تتضمن transfers:write.
V4 — سؤال. يسمح RFC 7519 للمتحقق بهامش سماح صغير لفروق الساعات بين الخوادم، لا يتجاوز عادةً بضع دقائق، ولا يذكر T3 شيئاً عن ذلك. ويُظهر V5 أن exp تُفحص، فالهامش يقع في مكان ما بين 40 ثانية و15 دقيقة. اسأل الفريق عن القيمة المضبوطة واطلب إضافتها إلى المواصفة، ثم اختبر الحدّ بقيمتين: رمز يُستخدم داخل الهامش مباشرة فيُقبل، ورمز يُستخدم بعد نهايته بثانية واحدة فيحصل على 401. وإن تبيّن أن الهامش أطول من بضع دقائق فأبلغ عنه عيباً، لأنه يطيل عمر كل رمز. V5 — صحيح: 401.
V6 — عيب (T2). النتيجة المتوقعة: 401، لأن aud هي cards-api لا accounts-api. فالرمز الصادر لخدمة يفتح خدمة أخرى، والرمز المسرَّب من واجهة البطاقات، أو من أي خدمة تتلقى مثل هذه الرموز، يفتح حسابات العملاء أيضاً. V7 — عيب له السبب الجذري نفسه: رمز الهوية موجّه إلى تطبيق الويب لا إلى واجهة API، ويقول الدليل إنه يجب ألا يُستخدم أبداً كرمز وصول. أبلغ عن V6 وV7 معاً في عيب واحد، «واجهة الحسابات لا تفحص aud»، مع الصفّين مثالين؛ فتقريران منفصلان يصفان السبب الجذري نفسه مرتين. الخطورة: رئيسي (وبعض الفرق تعدّه حرجاً). ويثير V7 سؤالاً أيضاً: رمز الهوية لا يحمل scope إطلاقاً ومع ذلك أعاد قائمة الحسابات، فاسأل هل تفحص الواجهة accounts:read، وأضف اختباراً لذلك.
V8 — صحيح: 401 بعد تسجيل الخروج، فقائمة الرفض المبنية على jti في T5 تعمل. لكنها لا تجعل عمراً يبلغ 24 ساعة بلا ضرر: فالعميل الذي لا يسجّل خروجه أبداً يترك رمزاً يبقى صالحاً يوماً كاملاً.
اختبارات تحقق لا يتضمنها السجل بعد (تكفي ثلاثة منها): ترويسة غُيّرت إلى HS256 مع رمز موقّع بسر HMAC من اختيارك، بأداة JWT Generator مثلاً، والنتيجة المتوقعة 401، وينبغي أن يُظهر سجل الواجهة أن الرفض كان بسبب الخوارزمية، لأن الخادم لا الترويسة هو من يختار الخوارزمية؛ وnone بحالة أحرف أخرى مثل None وNONE بعد الإصلاح، لأن الفحص الذي يحظر السلسلة none بحروفها هذه فقط قد يفوّتها؛ ورمز موقّع بمفتاح ES256 خاص بك بدلاً من مفتاح خادم المصادقة، والمتوقع 401، لأنه لا يجوز الوثوق إلا بالمفاتيح المنشورة في JWKS؛ ورمز من خادم المصادقة في بيئة التطوير (أي iss أخرى ومفتاح آخر)، ورمز تقع قيمة nbf فيه في المستقبل، والمتوقع لكليهما 401؛ ورمز وصول لا تتضمن scope فيه accounts:read، والمتوقع رفض الطلب (يوصي RFC 6750 بالرمز 403 عند عدم كفاية النطاق، فتأكد من الرمز المعتمد مع الفريق)؛ واختبار حدّ هامش السماح من V4. والرموز ذات المطالبات المختارة والتوقيع ES256 الصالح تحتاج إلى مساعدة فريق المصادقة على بيئة Staging، مثل عميل اختباري يصدرها؛ فاطلبها بدلاً من أن تتخطى هذه الاختبارات.
سؤال المطوّر: لا بالنسبة إلى national_id، وليس بهذه الصيغة بالنسبة إلى name. فالبيانات الشخصية لا مكان لها في رمز الوصول (T4). ورمز الهوية هو الرمز الموجّه إلى تطبيق الويب، وهو يحمل اسم العميل أصلاً (انظر V7)، فيمكن للصفحة أن تعرض الاسم من رمز الهوية أو من نقطة نهاية تعيد الملف الشخصي للعميل وصلاحياته، أما national_id فلا مكان له في أي من الرمزين. ثم إن قراءة رمز الوصول بـ JavaScript لا تنجح إلا لأن الرمز محفوظ في localStorage: فحين ينتقل إلى ملف تعريف ارتباط HttpOnly كما يشترط T6 لن تستطيع سكربتات الصفحة قراءته أصلاً، فلا بد من تغيير التصميم في كل الأحوال. أما إخفاء قائمة «التحويلات» فلا بأس به تسهيلاً في واجهة المستخدم، لكنه ليس ضابطاً أمنياً: فك الترميز ليس تحققاً، ويجب أن تفحص واجهة الحسابات transfers:write في كل طلب تحويل. وأضف اختباراً لذلك: طلب تحويل برمز لا تتضمن scope فيه transfers:write يجب أن يُرفض.
أبلغ عن V3 أولاً. مثال للعنوان: «واجهة الحسابات تقبل رموزاً غير موقّعة مع alg: none، وتغيير sub يعرض حسابات عميل آخر». وهو الأول لأنه لا يحتاج إلى رمز مسروق ولا إلى مفتاح، بل إلى رقم عميل فقط، ويكشف حسابات كل العملاء: الخطورة حرج، والأولوية هي الأعلى. ولأنه عيب أمني، أخبر المطوّرين فوراً واتبع إجراء فريقك للمسائل الأمنية، كتذكرة مقيّدة الوصول مثلاً. ثم عيب الجمهور (V6 وV7)؛ ثم العمر البالغ 24 ساعة والحفظ في localStorage، اللذان يحددان معاً كم من الوقت وبأي سهولة يمكن استخدام رمز مسروق؛ ثم البيانات الشخصية في الحمولة. وإن قدّم فريقك رقم الهوية الوطنية لأن قواعد حماية البيانات في المصرف تعدّه حادثة، فهذا سبب وجيه؛ المهم أن تتبع الأولوية الأثر. وبعد كل إصلاح أعد تنفيذ الصفوف المعنية اختباراتِ تأكيد (Confirmation testing)، وأعد تنفيذ V1 وV2 وV5 وV8 اختباراتِ انحدار (Regression)، لأن أي تغيير في شيفرة التحقق قد يكسر ما يعمل اليوم.
لتتحقق من عملك: يجب أن تكون لديك خمسة عيوب، ولا عيب في V1 أو V2 أو V5 أو V8. فإن صنّفت V2 عيباً، فأعد قراءة قسم «فك الترميز ليس تحققاً» في الدليل: الرفض بـ 401 لحمولة معدّلة هو بالضبط ما يجب أن يفعله المتحقق. وإن عددت تساوي nbf مع iat مشكلة، فأعد قراءة قسم المطالبات: nbf تحدد فقط متى أصبح الرمز صالحاً.
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.
جزء من مسار تعلم ضمان الجودة
قبل هذا الموضوع
الموضوعات التالية
الأدوات المذكورة في هذا الدليل
فكّ ترميز ترويسة JWT وحمولته، وفحص المطالبات (claims) والتحقق من انتهاء الصلاحية — بالكامل داخل متصفحك.
إنشاء رموز JWT موقَّعة (HS256 / HS384 / HS512) من مطالبات مخصّصة لأغراض الاختبار.
تحليل مطالبات JWT للكشف عن القيم المفقودة أو الخطرة أو غير القياسية مع توصيات أمنية.
التحقق مما إذا كان رمز JWT منتهي الصلاحية أو لم يصبح صالحاً بعد، أو المدة المتبقية حتى انتهاء صلاحيته.
فحص ترويسة JOSE لرمز JWT: alg وtyp وkid وjku وx5u ومخاطر الخلط بين الخوارزميات المحتملة.
فكّ ترميز رموز الوصول والهوية في OAuth 2.0 / OpenID Connect وفهم النطاقات والجماهير ومدد الصلاحية.
توليد توقيعات HMAC (SHA-256/384/512/SHA-1) لتوقيع طلبات webhook وواجهات API.
فكّ ترميز سلاسل Base64 أو Base64url إلى نص، مع إصلاح الحشو تلقائياً.