ما هو الـ JWT ؟

السلام عليكم ورحمة الله وبركاته

وقت القراءة: ≈ 5 دقائق

المقدمة

أولًا الـ 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 يتكون من ثلاث أجزاء

وستلاحظ ان الـ 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 ؟ وما السبب ؟