شرح JWT Authentication في Node.js مع حماية المسارات

شرح JWT Authentication في Node.js مع حماية المسارات

تحتاج الواجهات البرمجية الخاصة إلى طريقة تعرف بها هوية صاحب الطلب. من الطرق الشائعة استخدام JSON Web Token، وهو نص موقّع يرسل مع الطلبات اللاحقة. يفهم المطور أحياناً أن وجود التوقيع يعني أن التوكن سري، وهذا غير صحيح؛ محتوى JWT قابل للقراءة، والحماية تعتمد على عدم تسريبه والتحقق من توقيعه وصلاحيته. يشرح المقال نموذج تسجيل دخول تعليمي في Node.js، ثم يوضح middleware يحمي مساراً خاصاً. المثال لا يغطي كل متطلبات الإنتاج، لكنه يبين أين تتم عملية الإصدار وأين تتم عملية التحقق.



الفكرة الأساسية

يتكون JWT عادةً من رأس ومحتوى وتوقيع، وتفصل بينها نقاط. يضع الخادم معرف المستخدم ووقت الانتهاء في payload، ثم يوقع التوكن بمفتاح سري لا يعرفه العميل. عند وصول الطلب يقرأ middleware الترويسة Authorization، ويتحقق من التوقيع والانتهاء قبل وضع هوية المستخدم في request. لا تضع كلمة المرور أو معلومات حساسة داخل payload. كما يجب اختيار مكان التخزين في المتصفح بعناية، وتحديد سياسة انتهاء وتجديد وإلغاء مناسبة لطبيعة التطبيق.

مثال برمجي عملي

const jwt = require("jsonwebtoken");
const SECRET = process.env.JWT_SECRET;

function createToken(user) {
  return jwt.sign(
    { sub: user.id, role: user.role },
    SECRET,
    { expiresIn: "15m" }
  );
}

function requireAuth(req, res, next) {
  const header = req.get("Authorization") || "";
  const [scheme, token] = header.split(" ");

  if (scheme !== "Bearer" || !token) {
    return res.status(401).json({ error: "تسجيل الدخول مطلوب" });
  }

  try {
    req.user = jwt.verify(token, SECRET);
    next();
  } catch (error) {
    return res.status(401).json({ error: "التوكن غير صالح أو منتهي" });
  }
}

app.get("/api/profile", requireAuth, (req, res) => {
  res.json({ userId: req.user.sub, role: req.user.role });
});

شرح المثال

ينشئ createToken توكناً يحمل sub وrole وينتهي بعد مدة قصيرة. يجب أن يكون JWT_SECRET في متغير بيئة وليس في المستودع. يقرأ middleware الترويسة ويتأكد من وجود Bearer قبل استدعاء verify. عند النجاح يضع البيانات الموقعة داخل req.user ويمرر التحكم إلى المسار. عند الفشل يعيد 401 ولا يكشف هل السبب توقيع خاطئ أم توكن منتهي. تفترض الدالة وجود app وrequire للمكتبة، لذلك ضعها داخل تطبيق Express كامل مع إعداد مناسب.

ابدأ بتسجيل دخول تجريبي يعيد توكناً بعد التحقق من كلمة المرور، ثم أضف middleware لمسار واحد. اختبر غياب الترويسة، وتوكن منتهي، وتوكن معدل، ومستخدم بصلاحية غير كافية. افصل المصادقة عن التفويض؛ فإثبات الهوية لا يعني السماح بكل العمليات. عند استخدام refresh token، ضع له دورة حياة وسياسة تخزين مختلفة، ولا تمدد صلاحية access token بلا سبب.

طريقة العمل قبل كتابة الكود

قبل فتح محرر النصوص، اكتب النتيجة التي تريد الوصول إليها وحدد المدخلات والمخرجات. يساعد هذا التمرين على كشف الحالات الغامضة مبكراً، مثل قيمة ناقصة أو قائمة فارغة أو طلب لا يصل إلى الخادم. لا تحتاج إلى مخطط كبير للمثال التعليمي، لكنك تحتاج إلى أسماء واضحة وخطوات يمكن اختبارها واحدة بعد أخرى. عندما تتغير الفكرة أثناء الكتابة، عدل التصميم قبل إضافة شروط متفرعة يصعب تتبعها.

قسّم المشكلة إلى أجزاء صغيرة، واجعل كل جزء مسؤولاً عن قرار واحد قدر الإمكان. يمكن أن تكون الأجزاء دوال، أو مكونات، أو طبقات منفصلة بحسب التقنية. لا يعني التقسيم إنشاء ملفات كثيرة، بل يعني أن تعرف أين تبحث عندما يحدث الخطأ. سجّل الافتراضات المهمة بجملة قصيرة، مثل أن القائمة مرتبة أو أن السعر غير سالب، ثم تحقق منها في المكان المناسب بدلاً من الاعتماد على الذاكرة.

اختبار المثال في حالات مختلفة

لا تختبر المسار الطبيعي فقط. جرّب مدخلاً فارغاً، وقيمة أكبر من المتوقع، وعنصراً غير موجود، وطلباً يصل من دون البيانات المطلوبة. الحالات الحدية تكشف غالباً مشكلات الفهارس والأنواع وترتيب التنفيذ. اكتب النتيجة المتوقعة قبل تشغيل الكود، ثم قارنها بالنتيجة الفعلية. إذا كان الاختبار يمر بالصدفة، أضف شرطاً أو اختباراً أوضح، ولا تكتفِ بطباعة قيمة تبدو منطقية.

  • تحقق من المدخلات قبل استخدامها في الحساب أو التخزين.
  • أعد رسالة مفهومة ورمزاً مناسباً عند وقوع الخطأ.
  • اجعل الاختبارات قابلة للإعادة من دون اعتماد على شبكة أو وقت متغير.
  • راجع أثر التغيير على الأجزاء التي تستدعي الدالة أو المكون.

ملاحظات تتعلق بجودة الكود

تظهر جودة الحل في التفاصيل الصغيرة: اسم يشرح الغرض، ودالة لا تجمع مهاماً بعيدة، ورسالة خطأ لا تترك القارئ في حيرة. لا تحاول اختصار كل سطر، فالكود المقروء أفضل من تعبير قصير يحتاج إلى شرح طويل. وفي الوقت نفسه، لا تكرر القاعدة نفسها في أماكن كثيرة؛ انقلها إلى موضع واحد عندما يكون ذلك أوضح. راجع الملف بعد أن يعمل، لأن أول نسخة تركز عادةً على الوصول إلى النتيجة أكثر من قابلية الصيانة.

احتفظ بالإعدادات التي تختلف بين جهاز وآخر خارج الكود، ولا تضع كلمات مرور أو مفاتيح خاصة في المستودع. استخدم سجلات مناسبة أثناء التطوير، ثم راجع ما ينبغي حجبه في بيئة التشغيل. إذا تعامل البرنامج مع بيانات المستخدم، فافصل بين ما يحتاجه التطبيق وما يمكن الاحتفاظ به. هذه الممارسات لا تخص لغة واحدة، بل تقلل المشكلات عندما يكبر المشروع أو يعمل عليه أكثر من شخص.

متى تعرف أن الحل يحتاج إلى تطوير؟

يحتاج المثال التعليمي إلى طبقات إضافية عندما يدخل في نظام حقيقي: قاعدة بيانات، مستخدمون متعددون، مراقبة، اختبارات، وصلاحيات. لا تضف هذه الأجزاء قبل معرفة المشكلة التي تحلها، لكن لا تنقل الكود التجريبي إلى الإنتاج كما هو. راقب حجم البيانات، وعدد الطلبات، ومصدر المدخلات، وما إذا كان الفشل يجب أن يعيد العملية أو يوقفها. اكتب قرارك في مستند صغير أو تعليق يشرح السبب، حتى لا يضطر الفريق إلى تخمينه لاحقاً.

إذا وجدت أن الخطأ يتكرر في أكثر من مكان، فابحث عن قاعدة مشتركة. وإذا أصبح التعديل في ملف صغير يؤثر في ملفات كثيرة، فراجع حدود المسؤوليات. لا توجد بنية واحدة صحيحة لكل مشروع، لكن توجد أسئلة تساعدك على اختيار بنية مناسبة: من يملك البيانات؟ من يغيرها؟ ماذا يحدث عند الفشل؟ وكيف يمكن اختبار الجزء من دون تشغيل النظام كله؟

أخطاء ينبغي تجنبها

لا تخزن السر داخل الكود أو تضع بيانات حساسة في payload، ولا تقبل أي خوارزمية لمجرد أن التوكن قابل للقراءة. لا تستخدم 403 عندما لا توجد هوية، ولا 401 عندما تكون الهوية معروفة لكن الصلاحية ناقصة. من الأخطاء ترك access token طويلاً جداً أو عدم وجود طريقة لإلغاء الجلسة عند سرقة جهاز. استخدم HTTPS، وحدد معدل محاولات تسجيل الدخول، وسجل الأحداث المهمة من دون تسجيل التوكن نفسه.

خطوة تالية مناسبة

بعد المثال، اقرأ عن cookies الآمنة وSameSite، وقارنها بتخزين التوكن في الذاكرة أو localStorage حسب نوع التطبيق. أضف صلاحيات على مستوى العملية، ثم اكتب اختبارات للمسارات المحمية. المصادقة ليست قطعة كود واحدة، بل سلسلة قرارات تشمل كلمات المرور والجلسات والتسجيل والمراقبة.

التعامل مع انتهاء الرمز

لا ينبغي أن يتعامل التطبيق مع JWT كأنه صالح إلى الأبد. ضع مدة انتهاء مناسبة، وخطط لما يحدث عندما يعيد الخادم 401. قد تطلب الواجهة رمزاً جديداً عبر مسار تجديد منفصل، أو تطلب من المستخدم تسجيل الدخول مرة أخرى بحسب حساسية النظام. لا تجعل كل طلب يكرر منطق التحقق يدوياً؛ أنشئ middleware واحداً يقرأ الرأس، ويتحقق من التوقيع، ويضع هوية المستخدم في الطلب. اختبر أيضاً رمزاً ناقصاً ورمزاً منتهياً ورمزاً صحيحاً لمستخدم لا يملك الصلاحية. الفرق بين المصادقة والتفويض مهم: الأولى تثبت الهوية، والثانية تحدد ما يسمح لها بفعله.

الخلاصة

بعد المثال، اقرأ عن cookies الآمنة وSameSite، وقارنها بتخزين التوكن في الذاكرة أو localStorage حسب نوع التطبيق. أضف صلاحيات على مستوى العملية، ثم اكتب اختبارات للمسارات المحمية. المصادقة ليست قطعة كود واحدة، بل سلسلة قرارات تشمل كلمات المرور والجلسات والتسجيل والمراقبة. يوضح هذا الموضوع كيف يتحول مفهوم نظري إلى خطوات يمكن تشغيلها وفحصها.

ابدأ بتطبيق المثال على ملف صغير، ثم غيّر مدخلاً واحداً وراقب النتيجة. بعد ذلك أضف حالة فشل واكتب اختباراً لها، ثم انقل الفكرة إلى مشروعك الحقيقي بحذر. عندما تفهم سبب كل خطوة، ستستطيع تغيير الأدوات أو اللغة من دون فقدان المفهوم. البرمجة تتحسن بالمحاولات القصيرة والمراجعة المستمرة، لا بنسخ كود طويل من دون معرفة ما الذي يحميه أو ما الذي قد يكسره.

تعليقات