أولًا الـ 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 ؟ وما السبب ؟