الـ JWT مش مشفّر: أي حد ماسك التوكن يقدر يقرا محتواه
المستوى: متوسط. محتاج تكون عملت login بتوكن قبل كده وتعرف يعني API وheader وJSON. مش لازم خلفية أمان.
لو حاطط إيميل المستخدم أو صلاحيته أو أي بيانات جوّه الـ JWT وفاكر إنها "مشفّرة" لأن شكل التوكن طلاسم، دي أهم معلومة هتاخدها النهارده: أي حد ماسك التوكن يقدر يقراه كامل في ثانية. التوقيع بيمنع التزوير، مش القراءة. ركز في الفرق ده لأنه بيفرّق بين تطبيق آمن وتسريب بيانات صامت.
المشكلة باختصار
الـ JWT بقى الطريقة الافتراضية لإدارة الجلسات في الـ APIs. المطوّر بيحط فيه user_id وrole وأحيانًا إيميل أو رقم موبايل، وبيفكر إن التوقيع بيخفي ده كله. الافتراض ده غلط. الـ payload مقروء لأي طرف: الفرونت، المستخدم، أي بروكسي في النص، وأي حد سرق التوكن من الـ localStorage.
مثال يقرّب الصورة قبل التعريف العلمي
تخيّل إنك بتبعت جواب جوّه ظرف من زجاج شفّاف، ومختوم من بره بختم شمع أحمر بصمة خاصة بيك. الختم بيقول حاجة واحدة: "الجواب ده مني أنا، ومحدش عدّل فيه". لكنه لا يخفي أي كلمة مكتوبة جوّه، لأن الظرف شفّاف وأي حد يقرا من ورا الزجاج.
الـ JWT بيشتغل بنفس المنطق بالظبط. الـ payload ورا زجاج (ترميز Base64url)، والتوقيع هو ختم الشمع. الختم بيمنع التزييف، بس مش بيعمل ستارة على المحتوى.
علميًا: الـ JWT في صيغته الشائعة هو JWS بصيغة compact، وهو ثلاثة أجزاء مفصولة بنقطتين: header.payload.signature. كل جزء مكوّد بـ Base64url. الـ Base64url ترميز عكوس (reversible encoding) لتمثيل بايتات كنص آمن للـ URL، وهو ليس تشفيرًا: مفيش مفتاح، وأي طرف يقدر يفكّه. ده منصوص عليه في تعريف JWT الرسمي (RFC 7519).
جرّبها بنفسك: فك التوكن في 3 سطور
خُد أي JWT عندك وشغّل الكود ده. مش هتحتاج مفتاح ولا صلاحية:
import base64, json
token = "eyJhbGciOiJIUzI1NiJ9.eyJ 1c2VyIjoic2FyYSIsInJvbGUiOiJ1c2VyIn0.sig"
payload_b64 = token.split(".")[1]
payload_b64 += "=" * (-len(payload_b64) % 4) # ضبط حشو Base64
print(json.loads(base64.urlsafe_b64decode(payload_b64)))
# {'user': 'sara', 'role': 'user'}
الفك بياخد ميكروثواني، وحجم الـ payload النموذجي 150 إلى 300 بايت. يعني عمليًا: صفر جهد. أي بيانات حساسة حطيتها هناك تعتبر منشورة.
سيناريو واقعي: عندك API بـ 50 ألف مستخدم، وحطيت في التوكن plan وinternal_uid. أي مستخدم يقدر يقرا الترقيم الداخلي بتاعك وتركيبة الباقات، ويبني عليها هجوم أو تعداد للحسابات. ما اتسربش مفتاح؛ التصميم نفسه بيسرّب.
الخطر الأكبر: هجوم alg none
لو فاكر إن القراءة بس هي المشكلة، فيه أسوأ. معيار JWT بيسمح بقيمة alg تساوي none معناها "توكن بدون توقيع". مكتبات كتير في 2014 و2015 كانت بتقرا الخوارزمية من ترويسة التوكن نفسه وتنفّذها زي ما هي. النتيجة كارثية.
الهجوم بيمشي كده: المهاجم بياخد توكن صالح، يغيّر الترويسة لـ none، يعدّل الـ payload من role user لـ role admin، ويمسح التوقيع خالص. المكتبة اللي بتثق في ترويسة التوكن بتقبله. ده مسجّل رسميًا كثغرة CVE-2015-9235 في مكتبة jsonwebtoken قبل الإصدار 4.2.2، واتكرر في مكتبات تانية.
الإصلاح: ثبّت الخوارزميات وما تثقش في الترويسة
القاعدة: انت اللي بتقرر الخوارزمية المقبولة، مش التوكن. ثبّتها صراحةً وتحقق من المُصدر:
const jwt = require("jsonwebtoken");
// غلط: بيسيب المكتبة تقرا alg من التوكن نفسه
// const claims = jwt.verify(token, key);
// صح: ثبّت الخوارزمية المسموحة، وتحقق من المُصدر
const claims = jwt.verify(token, key, {
algorithms: ["HS256"],
issuer: "https://api.example.com",
});
الخطوة دي وحدها بتقفل هجوم alg none وهجوم خلط الخوارزميات (RS256 يتحوّل لـ HS256). التحقق من المُصدر iss بيمنع استبدال التوكنات، وده من التوصيات الأساسية في مرجع أفضل الممارسات (RFC 8725).
لو محتاج تخفي البيانات فعلًا: JWE
لو عندك بيانات لازم تتخفي على القارئ (مش بس متتزوّرش)، محتاج تشفير حقيقي: JWE في RFC 7516، اللي بيشفّر الـ payload بحيث ميتقراش من غير مفتاح. لكن الأبسط والأرخص في معظم الحالات: متحطش بيانات حساسة في التوكن من الأساس. حط user_id بس، وهات الباقي من قاعدة البيانات وقت الحاجة.
الـ trade-offs بوضوح
- JWT بلا حالة (stateless): بتكسب توفير استعلام جلسة من قاعدة البيانات في كل طلب (عادةً من 1 إلى 5 مللي ثانية). بتخسر القدرة على الإلغاء الفوري: التوكن بيفضل صالح لحد ما ينتهي، حتى لو عملت logout.
- JWE بدل JWS: بتكسب سرّية المحتوى. بتخسر بساطة، وحجم أكبر، ومعالجة أثقل، ومعظم الأنظمة مش محتاجاه.
- عمر توكن قصير: بيقلّل نافذة الخطر لو اتسرب. بيزوّد ضغط تجديد التوكنات على الـ auth server.
متى لا تستخدم JWT
ما تستخدمهوش كـ session store لو محتاج logout فوري أو حظر حساب لحظي؛ الجلسة في Redis أنسب هنا. وما تستخدموش لتخزين بيانات حساسة اعتمادًا على "إنه مشفّر"، لأنه مش مشفّر. لو عندك سيرفر واحد وأقل من بضعة آلاف مستخدم، الجلسة التقليدية أبسط وأأمن غالبًا.
الخطوة التالية
افتح أي توكن بيصدر من نظامك دلوقتي على jwt.io أو بالكود اللي فوق، وبُص على الـ payload. لو لقيت أي حاجة أكتر من معرّفات لازمة (زي إيميل أو رقم موبايل أو حقول داخلية)، شيلها. وبعدين افتح كل jwt.verify في الكود بتاعك وتأكد إن الخوارزميات متحدّدة صراحةً. لو مش متحدّدة، ده أول باب لازم تقفله.
المصادر
- تعريف JWT الرسمي — RFC 7519: datatracker.ietf.org/doc/html/rfc7519
- أفضل الممارسات الحالية لـ JWT — RFC 8725: datatracker.ietf.org/doc/html/rfc8725
- تشفير الحمولة JSON Web Encryption — RFC 7516: datatracker.ietf.org/doc/html/rfc7516
- ثغرة alg none في jsonwebtoken — CVE-2015-9235: nvd.nist.gov/vuln/detail/CVE-2015-9235