4.2 · التوثيق (authentication) والأمن (الترميز (JWT) وbcrypt وPassport وHelmet)

المستوى 4 · واجهة برمجة التطبيقات والتوثيق والزمن الحقيقي (API, authentication & real-time)

4.2التوثيق (authentication) والأمن (الترميز (JWT) وbcrypt وPassport وHelmet)

الهدف: تأمين التوثيق (authentication) (كلمات مرور مجزأة بـ bcrypt، ورموز ترميز (JWT) متحقق منها، وتخويل) وتطبيق أسس الأمن (Helmet، وأسرار محمية).
الوقت المقدّر: 13 دقيقة · قراءة ذاتية · إذا كان لديك 5 دقائق فقط: اقرأ «الأساسي في 30 ثانية» + المخطط.

الأساسي في 30 ثانية

  • أبدا كلمة مرور بالنص الصريح : نخزن تجزئة bcrypt (باتجاه واحد، ومملحة، وبطيئة) ونقارن عند الاتصال دون فك تشفير أبدا.
  • الترميز (JWT) = رمز موقع محمول ذاتيا : يُصدر بعد الاتصال، ويُرجع في ترويسة Authorization، ويتحقق منه وسيط (401 غير موثق، و403 غير مخول).
  • منعكس ساليم : سر الترميز (JWT) في متغير بيئة (وليس في الكود أبدا)، وHelmet مفعَّل، و« لا ثقة أبدا بالمدخلات ».

حالة مصغرة متواصلة — ساليم يؤمن حسابات الواجهة

واجهة منتجات ساليم (4.1) يجب أن تدبر باعة الآن : تسجيل، واتصال، ومسارات محجوزة. أول منعكس : تجزئة كل كلمة مرور بـ bcrypt قبل التخزين، ثم إصدار رمز ترميز موقع عند الاتصال الناجح. يكتب وسيطا يتحقق من الرمز على المسارات المحمية، ويميز 401 (غير متصل) و403 (دون حق)، ويفعل Helmet ويضع سر الترميز في متغير بيئة. وعندما يتصل بائع، يرجع عميله الرمز مع كل طلب — دون كشف كلمة مرور أبدا. ثاني محطة في الخيط الرابط : حسابات منيعة حتى لو تسربت القاعدة.

تتمة حالة 4.1 — يحمي ساليم الواجهة التي بناها للتو.

1. كلمات المرور : bcrypt، وليس النص الصريح أبدا

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

لا تبرمجوا أبدا تجزئتكم « البيتيّة » : استعملوا bcrypt أو مكافئا مجربا. وتذكروا أن كثيرا من المستخدمين يعيدون استعمال كلمات مرورهم في مواضع أخرى : تسرب بالنص الصريح يعرضهم بعيدا أبعد من تطبيقكم.

2. الحفاظ على الجلسة : الترميز (JWT)

HTTP دون حالة : بعد الاتصال، يجب التعرف على المستخدم مع كل طلب. والترميز (JWT) (JSON Web Token) رمز موقع بالتشفير، يُصدر بعد اتصال ناجح ؛ يرجعه العميل مع كل طلب (ترويسة Authorization: Bearer ...) ويتحقق منه الخادم — دون تخزين جلسة، فالترميز « محمول ذاتيا ». وهو المقاربة الأشهر للواجهات والهاتف.

يحمي وسيط المسارات : رمز غائب أو غير صالح ← 401 ؛ ورمز صالح لكن حقوقا ناقصة ← 403. ويبسط Passport التوثيق (استراتيجيات محلية، وOAuth من Google/GitHub). احفظوا التمييز : التوثيق (من أنت ؟ 401) مقابل التخويل (هل لك الحق ؟ 403).

3. الأمن الشامل

أبعد من التوثيق : Helmet (وسيط Express يضع ترويسات HTTP للأمن)، وHTTPS في الإنتاج، وأسرار في متغيرات البيئة (.env يتجاهله Git، وليس في الكود أبدا)، وتحديد المعدل ضد القوة الغاشمة، والتحقق من المدخلات (انظر 4.4). والمبدأ الكامن وراء كل شيء : لا ثقة أبدا بالمدخلات ولا بالعميل — فكل ما يأتي من الخارج خبيث بالقوة. الأمن ليس خيارا يضاف في الآخر، بل انشغال دائم، مدمج منذ التصميم.

خطأ شائع في الفهم: « وضعت التوثيق برمز ترميز، إذن واجهتي مؤمنة ». لا. لا يفعل الترميز إلا الحفاظ على الهوية : دون bcrypt (كلمات مرور بالنص الصريح)، ودون سر محمي (مفتاح في الكود مدفوع على Git)، ودون Helmet ولا HTTPS، ودون تحقق من المدخلات ودون تمييز 401/403، يبقى البناء مصفاة. الأمن حزمة تدابير، وليس لبنة واحدة.
الدورة الموثقة التسجيل ← الاتصال ← طلبات متحقق منها التسجيل bcrypt يجزئ،ونخزن التجزئةالاتصال نقارن، ونصدررمزا موقعاالطلبات يرجع العميل الرمز(Authorization)الوسيط يتحقق : 401 أو403، أو req.user
التسجيل ← الاتصال ← طلبات متحقق منها
نص المخطط (SEO / قارئات الشاشة / الترجمة): 1) عند التسجيل، يجزئ ساليم كلمة المرور بـ bcrypt ولا يخزن إلا التجزئة. 2) عند الاتصال، يقارن ثم يصدر رمزا موقعا. 3) يرجع العميل هذا الرمز في ترويسة Authorization مع كل طلب. 4) يتحقق منه وسيط على المسارات المحمية : 401 إذا غاب أو كان غير صالح، و403 إذا انعدم الحق. الدرس : جزئ، ووقع، وتحقق — والسر خارج الكود.

🔎 مثال ملموس — 🔎 مثال ملموس — التسجيل والاتصال والمسار المحمي

الدائرة الكاملة لساليم، والسر مقروء من البيئة :

POST /inscription  ← bcrypt.hash(motDePasse) ← تخزين التجزئة
POST /connexion   ← bcrypt.compare ← jwt.sign({ id }, SECRET)
GET  /mon-compte  ← Authorization: Bearer ... ← الوسيط يتحقق
  ← رمز غائب/غير صالح : 401 · دون حق : 403

القراءة : ثلاثة مسارات، وثلاث مسؤوليات متميزة.

الدرس : التوثيق (من ؟) والتخويل (أي حق ؟) مرشحان.

مفردات القسم

bcrypt
مكتبة تجزئة كلمات المرور : باتجاه واحد، وبملح عشوائي، وبطيئة عمدا ضد القوة الغاشمة.

مثال بسيط: يجزئ ساليم عند التسجيل ويقارن عند الاتصال، دون فك تشفير أبدا.

ما ليس كذلك: تشفير عكوس أو تجزئة « بيتيّة ».

رد فعل: كلمة مرور بالنص الصريح في القاعدة = خطأ جسيم، جزئ دائما.

الترميز (JWT)
رمز موقع يُصدر بعد الاتصال، يرجعه العميل مع كل طلب ويتحقق منه الخادم ؛ دون حالة، مثالي للواجهات.

مثال بسيط: يستلم بائع ساليم رمزه عند الاتصال ويرجعه في Authorization.

ما ليس كذلك: جلسة مخزنة لدى الخادم مع ملف تعريف.

رد فعل: التحقق من التوقيع مع كل طلب محمي.

التوثيق مقابل التخويل
التوثيق : من هو المستخدم (401 إذا انعدم). التخويل : هل له الحق (403 إذا انعدم).

مثال بسيط: يرجع ساليم 401 دون رمز، و403 لبائع على مسار إداري.

ما ليس كذلك: مراقبة وحيدة ضبابية تخلط بينهما.

رد فعل: اختيار الرمز الصحيح : 401 أم 403 ؟

Helmet
وسيط Express يحدد ترويسات HTTP للأمن تحمي من ثغرات ويب كلاسيكية.

مثال بسيط: يفعله ساليم في سطر واحد : app.use(helmet()).

ما ليس كذلك: نظام توثيق كامل وحده.

رد فعل: تفعيله افتراضيا على كل تطبيق Express.

متغير البيئة
إعداد مقروء من البيئة (ملف .env يتجاهله Git)، وهو الموضع الوحيد المقبول للأسرار.

مثال بسيط: سر ترميز ساليم يعيش في process.env.JWT_SECRET، وليس في الكود أبدا.

ما ليس كذلك: مفتاح ملصق في الكود مدفوع على Git.

رد فعل: .env يتجاهله Git + مثال .env.example دون قيم.

في التطبيق — عمليا — تأمين التوثيق

  1. جزئوا كلمات المرور بـ bcrypt عند التسجيل (وليس بالنص الصريح أبدا) وقارنوا عند الاتصال (bcrypt.compare).
  2. أصدروا رمزا موقعا بعد اتصال ناجح، بسر مقروء من process.env (وليس في الكود أبدا).
  3. اكتبوا وسيطا يتحقق من الرمز على المسارات المحمية (401 إذا غاب/غير صالح) ويربط المستخدم بـ req.user.
  4. أضيفوا Helmet، وضعوا كل الأسرار في متغيرات البيئة (.env يتجاهله Git) وميزوا 401 / 403.
تؤمنون التوثيق (كلمات مرور مجزأة، ورموز متحقق منها، وتخويل) وتطبقون الأسس (Helmet، وأسرار محمية) — مسؤولية مهنية.

نص جاهز للقول — شرح خياراته الأمنية في المراجعة (30 ثانية)

« معكم ساليم : لا كلمة مرور بالنص الصريح، فكل شيء مجزأ بـ bcrypt ؛ وبعد الاتصال نصدر رمزا موقعا سره يعيش في متغير بيئة، وليس في الكود أبدا. يتحقق وسيط من الرمز على المسارات المحمية — 401 دون رمز صالح، و403 دون حق — ويضع Helmet ترويسات أمننا. »

يُقال في مراجعة الكود ؛ واعرضوا .gitignore الذي يستبعد .env.

نموذج للنسخ واللصق — توثيق bcrypt + ترميز + Helmet (كود مختبَر)

require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');

const app = express();
app.use(helmet());
app.use(express.json());

const SECRET = process.env.JWT_SECRET; // ليس في الكود أبدا !
if (!SECRET) throw new Error('JWT_SECRET manquant : définissez-le dans .env');

const vendeurs = []; // عرض : في الحقيقة، قاعدة بيانات

// التسجيل : نجزئ، ونخزن التجزئة
app.post('/inscription', async (req, res) => {
  if (!req.body.email || !req.body.motDePasse) {
    return res.status(400).json({ erreur: 'Email et mot de passe requis' });
  }
  const hachage = await bcrypt.hash(req.body.motDePasse, 10);
  vendeurs.push({ email: req.body.email, hachage });
  res.status(201).json({ message: 'Compte créé' });
});

// الاتصال : نقارن، ونصدر رمزا
app.post('/connexion', async (req, res) => {
  const vendeur = vendeurs.find((v) => v.email === req.body.email);
  if (!vendeur) return res.status(401).json({ erreur: 'Identifiants invalides' });
  const ok = await bcrypt.compare(req.body.motDePasse, vendeur.hachage);
  if (!ok) return res.status(401).json({ erreur: 'Identifiants invalides' });
  const jeton = jwt.sign({ email: vendeur.email }, SECRET, { expiresIn: '1h' });
  res.json({ jeton });
});

// الوسيط : يتحقق من رمز المسارات المحمية
function verifierJeton(req, res, next) {
  const entete = req.headers.authorization || '';
  const jeton = entete.replace('Bearer ', '');
  if (!jeton) return res.status(401).json({ erreur: 'Jeton manquant' });
  try {
    req.utilisateur = jwt.verify(jeton, SECRET);
    next();
  } catch (err) {
    return res.status(401).json({ erreur: 'Jeton invalide' });
  }
}

app.get('/mon-compte', verifierJeton, (req, res) => {
  res.json({ email: req.utilisateur.email });
});

app.listen(3000);

مشروع شخصي صغير

يكفي bcrypt + الترميز + Helmet : لا تعقدوا بـ OAuth ما دام المحلي يغطي الحاجة.

الاتصال عبر Google/GitHub

أضيفوا Passport باستراتيجية OAuth : فوضوا الهوية، واحتفظوا بالترميز لمساراتكم.

واجهة معرضة في الإنتاج

كما يفعل ساليم : HTTPS إجباري، ورمز بمدة قصيرة، وتحديد معدل على /connexion ضد القوة الغاشمة.

فريق يبدأ

طقس لا مساومة فيه : لا سر في الكود ولا في Git — مراجعة منهجية قبل كل دفع.

حذر: الأمن يلزم مسؤوليتكم : أبدا كلمة مرور بالنص الصريح (bcrypt إجباري)، وأسرار الترميز والمفاتيح في متغيرات البيئة (.env يتجاهله Git، وليست في الكود أبدا)، وHelmet مفعَّل وHTTPS في الإنتاج. تستعمل أمثلة هذا الدليل بيانات وهمية فقط — لا سر حقيقي. وعند الشك في بيانات شخصية، اعرضوا البنية على مرجع تقني. محتوى تعليمي، وليس تدقيقا أمنيا.

✓ ممارسات جيدة

  • التجزئة بـ bcrypt، والمقارنة دون فك تشفير.
  • سر الترميز في متغير بيئة، وليس في الكود أبدا.
  • تمييز 401 (من ؟) و403 (أي حق ؟).

✗ أخطاء شائعة

  • تخزين كلمات مرور بالنص الصريح.
  • لصق سر الترميز في الكود أو على Git.
  • الخلط بين التوثيق والتخويل.

النقاط الأساسية

  • أبدا كلمة مرور بالنص الصريح : تجزئة BCRYPT (باتجاه واحد، وملح، وبطيئة) ؛ والمقارنة عند الاتصال. أبدا تجزئة بيتيّة.
  • الترميز : رمز موقع محمول ذاتيا (Authorization)، يتحقق منه وسيط ؛ ويبسط Passport (محلي، OAuth).
  • التوثيق (401) مقابل التخويل (403) — مراقبتان متميزتان.
  • Helmet وHTTPS والأسرار في متغيرات البيئة، وتحديد المعدل، والتحقق. المبدأ : لا ثقة أبدا بالمدخلات.

تحدي 48 ساعة — طبّق غدا

أضيفوا تسجيلا + اتصالا + مسارا محميا واحدا إلى واجهتكم : bcrypt، وترميز عبر process.env، وHelmet — ثم تحققوا من 401 دون رمز.

قائمة الخروج من القسم
  • أستطيع شرح bcrypt والترميز و401 مقابل 403.
  • أعرف وضع سر في متغير بيئة.
  • لدي مسار محمي واحد مختبَر مع رمز ودونه.
تقييم ذاتي 1-5 : لا سر في كودي ؟هل تميز مساراتي الحساسة بين 401 و403 ؟

أسئلة شائعة

لماذا لا نخزن كلمات المرور بالنص الصريح أبدا، وكيف يحمي bcrypt ؟

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

من أين نبدأ عندما يبدو الأمن مخيفا ؟

من الذهنية « لا ثقة أبدا بالمدخلات » — فهي وحدها تمنع قسما كبيرا من الثغرات — ثم بالأسس عالية الأثر : bcrypt لكلمات المرور، وأسرار في متغيرات البيئة (وليست في Git أبدا)، وHTTPS في الإنتاج، وتوثيق وتخويل صحيحين (401/403)، والتحقق من المدخلات (4.4)، وHelmet. يُدمج الأمن منذ التصميم، وليس في الآخر.

تحقّق من فهمك

أين يجب أن يعيش سر توقيع الترميز ؟

موارد إضافية (اختياري، للتعمّق أكثر)

اختبار المستوى 4أسئلة جديدة وأصعب للتحقق من مكتسباتك