أولًا الـ JWT هو اختصار لـ Json Web Token
والـ Json كما نعرف هو الشكل المتعارف عليه في تخزين البيانات
والـ token يمكنك أن تتخيله كبطاقة تعرفية مشفرة
فببساطة الـ JWT يستطيع تشفير الـ Json لـ Token
الآن لنفترض أنه لدينا بيانات المستخدم ونريد عمل token له
const user = { id: 1, name: 'Ahmed', email: 'ahmed@gmail.com', };
نظرة أولية عن الـ JWT
الـ JWT سيحتاج منك بعض لأشياء لكي ينشيء الـ token
منها البيانات التي تريد تشفيرها بالطبع
والـ Secret، هي جملة تبتكرها لتكون لكلمة سر تستخدم في التشفير لتزيد من قوة التشفير
ويتحسن أن نعطيه تاريخ انتهاء صلاحية هذا الـ token
لضمان حماية المستخدم بشكل افضل، عن طريق انه يجدد الـ token كل فترة
// backend const SECRET_KEY = 'هذه كلمة سر للتشفير بالغة السرية، لا تشاركها مع أحد'; const token = jwt.sign(user, SECRET_KEY, { expiresIn: 60 * 60 * 24, // 1 day }); console.log(token);
شكل الـ token سيكون هكذا
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwibmFtZSI6IkFobWVkIiwiZW1haWwiOiJhaG1lZEBnbWFpbC5jb20iLCJpYXQiOjE2NzExNDY2MDIsImV4cCI6MTY3MTIzMzAwMn0.AwMn0.ozR5GdDwM-aajZe_X2rjFnTqaN83FJu1Vya-f95h5_U
كيف يتم إنشاء الـ token ؟
أول شيء عليك أن تعرفه أن كل Token يتكون من ثلاث أجزاء
- header
- payload
- signature
وستلاحظ ان الـ token شكله يشبه هذا xxx.yyy.zzz
بمعنى جملة مشفرة ثم نقطة . جملة مشفرة ثم نقطة . جملة مشفرة
header.payload.signature
أولًا الـ header
به معلومات تخص التشفير مثل خوارزمية التشفير ونوع التشفير
const header = { alg: 'HS256', // خوارزمية التشفير typ: 'JWT', // نوع التشفير };
ثانيًا الـ payload
به المعلومات الفعلية التي شفرناها
const payload = { id: 1, name: 'Ahmed', email: 'ahmed@gmail.com', iat: 1671146602, // تاريخ الإنشاء exp: 1671233002, // تاريخ انتهاء الصلاحية // ستلاحظ أن الفرق ما بينهم هو 1 يوم بالميلي ثانية };
ثالثًا الـ signature
وهو عبارة عن تهيش (hashing) للـ header و الـ payload مع الـ secret
ملحوظة الـ header والـ payload يتم تحويلهما لـ base64
const signature = HS256( base64(header) + '.' + base64(payload), SECRET_KEY );
بالتالي يمكننا تخيل شكل الـ token هكذا
const token = base64(header) + '.' + base64(payload) + '.' + signature;
استعمال الـ token
بعد ما عرفنا كيف يتكون الـ token الآن دعونا نعرف كيف نستعمله بشكل عملي
وكيف يكون الأمر في المشاريع الكبيرة
الـ frontend سيستقبل بيانات المستخدم ثم يرسلها للـ backend
ثم يقوم الـ backend بعمل الـ token ويرسلها للـ frontend
يستقبل الـ frontend الـ token ويخزن في أي مكان في المتصفح مثلا في localStorage
sendDataToBackend(user).thenOnReceiveToken((token) => { storeInLocalStorage(token); // بعد ما نحصل عليه نخزنه });
استخدام الـ id فقط!
هل نُنشيء الـ token من كامل بيانات المستخدم ؟
لا، لانه حينها أي تعديل طفيف وبسيط على أي شيء من بيانات المستخدم
فسيتوجب علينا إنشاء token جديدة بناءًا على هذا التغير
لذا يستحسن دائما أن نقوم بعمل الـ token عن طريق الـ id الخاص بالمستخدم
const token = jwt.sign({ id: user.id }, SECRET_KEY, { expiresIn: 60 * 60 * 24, // 1 day });
فكر بالـ token على أنه بطاقة الهوية الخاصة بك في الشركة
عندما تغير عنوانك أو رقم هاتفك أو تكبر في العمر سنة أو … أو …
هل ستتأثر بطاقة الهوية الخاصة بك في الشركة ؟
هل تغير شيء شخصي لك مثل محل سكنك أو شراء سيارة جديدة سيتوجب عليك تغير هذه البطاقة ؟
بالطبع لا، لذا نُنشيء الـ token عن طريق شيء فريد ومميز للشخص مثل الـ id لانه دائمًا ثابت
متى نقوم بإنشاء الـ token
نقوم بإنشاءه عندما يقوم المستخدم بعمل تسجيل دخول أو إنشاء حساب جديد
// endpoint: /api/users/signup // method: POST // body: user // receive: token const token = signup(user); // token لنحصل على الـ backend نرسل البيانات للـ storeInSession(token); // بعد ما نحصل عليه نخزنه كما قلنا
نفس الأمر مع endpoint الـ login
بمعنى اننا نُنشيء الـ token في الـ login والـ signup فقط
وهذا منطقي لانك ان دخلت الشركة فستحصل على بطاقة تعرفية حينها
أين نستخدم الـ token
عندما يقوم المستخدم بالقيام بأي عملية داخل الموقع
فإن الـ frontend يرسل الـ token مع الـ requests
لكي يقوم الـ backend بالتحقق من الـ token
التحقق من أن الـ token مزيف ام لا وهل هو منتهي الصلاحية
const payload = jwt.verify(token, SECRET_KEY); const user = userDatabase.findById(payload.id); // التحقق من أن الشخص موجود فعلًا لدينا // تم إنشاءه قبل آخر تعديل لكلمة السر ام لا token ايضًا بالتحقق هل هذا الـ backend يقوم الـ isPasswordChangedAfterTokenCreated(user.passwordChangedAt, payload.iat);
علاقة الـ token بتغير كلمة السر
لما يتحقق من تاريخ آخر تعديل لكلمة السر ؟
لنفرض أن المستخدم قرر تغير كلمة السر
في هذه الحالة يجب أن ننشيء token جديد له
ونعطل كل الـ token القديمة التي أنشئت قبل تغير كلمة السر
سبب هذا بسيط لنفرض أن شخص ما سرق حسابك، هكذا هو يملك token في المتصفح الخاص به
فأنت تقوم بسرعة بتغير كلمة السر
في هذه الحالة على الـ backend تعطيل جميع الـ token السابقة
هكذا الشخص الذي سرق حسابك عندما يقوم بأي عملية باستخدام الـ token القديم
سيقوم الـ backend بالتحقق من تاريخ إنشاء الـ token مع تاريخ تغير كلمة السر
const isTokenOld = isPasswordChangedAfterTokenCreated( user.passwordChangedAt, payload.iat ); if (isTokenOld) SendToFrontend('User not authorized, Please log in');
سيجد أن الـ token قديم بالتالي سيقول له سجل دخولك مجددًا
وهكذا بالطبع لن يستطيع السارق أو المخترق التسجيل مجددًا لانه لا يعرف كلمة السر الجديدة
والـ token الذي حصل عليه اصبح قديم
هذه كانت بعض المعلومات الصغيرة التي اردت ان ابسطها هنا
الآن سؤال لك أريدك أن تفكر فيه جيدًا
بعد ما فهمت إن شاء الله طريقة عمل الـ token
هل إن استغنينا عن الـ JWT وقررنا التعامل مع الـ id كـ token
هل هذا يغني عن الـ token ؟ وما السبب ؟