4.2التوثيق (authentication) والأمن (الترميز (JWT) وbcrypt وPassport وHelmet)
الأساسي في 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). والمبدأ الكامن وراء كل شيء : لا ثقة أبدا بالمدخلات ولا بالعميل — فكل ما يأتي من الخارج خبيث بالقوة. الأمن ليس خيارا يضاف في الآخر، بل انشغال دائم، مدمج منذ التصميم.
🔎 مثال ملموس — 🔎 مثال ملموس — التسجيل والاتصال والمسار المحمي
الدائرة الكاملة لساليم، والسر مقروء من البيئة :
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 دون قيم.
في التطبيق — عمليا — تأمين التوثيق
- جزئوا كلمات المرور بـ bcrypt عند التسجيل (وليس بالنص الصريح أبدا) وقارنوا عند الاتصال (bcrypt.compare).
- أصدروا رمزا موقعا بعد اتصال ناجح، بسر مقروء من process.env (وليس في الكود أبدا).
- اكتبوا وسيطا يتحقق من الرمز على المسارات المحمية (401 إذا غاب/غير صالح) ويربط المستخدم بـ req.user.
- أضيفوا Helmet، وضعوا كل الأسرار في متغيرات البيئة (.env يتجاهله Git) وميزوا 401 / 403.
نص جاهز للقول — شرح خياراته الأمنية في المراجعة (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 — مراجعة منهجية قبل كل دفع.
.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.
- أعرف وضع سر في متغير بيئة.
- لدي مسار محمي واحد مختبَر مع رمز ودونه.
أسئلة شائعة
لماذا لا نخزن كلمات المرور بالنص الصريح أبدا، وكيف يحمي bcrypt ؟
إذا تسربت القاعدة (قرصنة، ونسخة احتياطية مسروقة — ويحدث هذا بانتظام)، تعرض كلمات المرور بالنص الصريح كل الحسابات، والأسوأ الخدمات الأخرى حيث يعيد المستخدمون استعمال كلمة المرور نفسها. التجزئة باتجاه واحد : نخزن التجزئة عند التسجيل، ونقارن عند الاتصال، دون القدرة « على استرجاع » كلمة المرور أبدا. يضيف bcrypt ملحا عشوائيا (ضد الجداول المحسوبة مسبقا) وبطئا مقصودا (ضد القوة الغاشمة). سرقة تجزئات bcrypt لا تسمح بالاتصال مباشرة.
من أين نبدأ عندما يبدو الأمن مخيفا ؟
من الذهنية « لا ثقة أبدا بالمدخلات » — فهي وحدها تمنع قسما كبيرا من الثغرات — ثم بالأسس عالية الأثر : bcrypt لكلمات المرور، وأسرار في متغيرات البيئة (وليست في Git أبدا)، وHTTPS في الإنتاج، وتوثيق وتخويل صحيحين (401/403)، والتحقق من المدخلات (4.4)، وHelmet. يُدمج الأمن منذ التصميم، وليس في الآخر.
أين يجب أن يعيش سر توقيع الترميز ؟