ما هو الـ JWT ؟
السلام عليكم ورحمة الله وبركاته
الفهرس
المقدمة
أولًا الـ JWT
هو اختصار لـ Json Web Token
والـ Json
كما نعرف هو الشكل المتعارف عليه في تخزين البيانات
والـ token
يمكنك أن تتخيله كبطاقة تعرفية مشفرة
فببساطة الـ JWT
يستطيع تشفير الـ Json
لـ Token
الآن لنفترض أنه لدينا بيانات المستخدم ونريد عمل token
له
const user = {
id: 1,
name: 'Ahmed',
email: '[email protected]',
};
نظرة أولية عن الـ 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: '[email protected]',
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
؟ وما السبب ؟