كيفية التعامل بشكل آمن مع كلمات المرور باستخدام BcryptsJS في لغة JavaScript

المقدمة

حماية كلمات مرور المواقع هي مهارة أساسية يجب أن يمتلكها أي مطور. يقدم JavaScript خيارًا لضمان تخزين ومعالجة آمنة لكلمات المرور أو البيانات الحساسة الأخرى باستخدام خوارزميات التجزئة المقدمة من وحدة BcryptJS في JavaScript.

في هذا البرنامج التعليمي، ستتعلم حول BcryptJS وعملية التجزئة لإعداد خادم express أساسي سيقوم بتخزين كلمات المرور كتجزئة في قاعدة بيانات بدلاً من سلاسل نصية خامة واسترجاعها للتحقق من كلمة المرور.

المتطلبات المسبقة

لمتابعة هذا البرنامج التعليمي، يجب أن يكون لديك الإعداد التالي.

  1. إصدار ثابت من Node.js مثبت على جهاز الكمبيوتر الخاص بك بالإصدار 12.x أو أعلى. يمكنك استخدام هذا البرنامج التعليمي من DigitalOcean لتثبيت أحدث إصدار من Node Js على جهاز الكمبيوتر الخاص بك.

  2. يجب أن تعرف كيفية كتابة الشيفرة بلغة JavaScript.

  3. _يجب أن يكون لديك Express JS مثبتًا على جهاز الكمبيوتر الخاص بك. يمكنك استخدام هذا الدليل لتعلم كيفية إعداد خادم Express.

  4. أخيرًا ، ستحتاج إلى قاعدة بيانات MongoDB المجتمعية أو أطلس لإكمال هذا البرنامج التعليمي. يمكنك تثبيته باستخدام أحد هذه الدلائل من DigitalOcean على_ كيفية تثبيت MongoDB.

لماذا استخدام BcryptJS؟

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

BcryptJS هو تنفيذ JavaScript لخوارزمية تجزئة Bcrypt، مما يتيح لك استخدام تشفير التجزئة دون الحاجة إلى التورط في وظائف تجزئة معقدة. بعض الأسباب التي تجعل BcryptJS خيارًا رائعًا لأمان كلمات المرور هي الآتية:

  1. الأمان – ينفذ BcryptJS خوارزمية Bcrypt، وهي خوارزمية بطيئة (وهو أمر جيد في التجزئة) وتتطلب قوة حاسوبية كبيرة. يجعل ذلك من المهمة صعوبة بالغة بالنسبة للمهاجمين كسر تجزئة كلمة المرور، مما يضمن سلامة كلمات المرور حتى في حالة انتهاك البيانات._

  2. تمليح – يدير BcryptJS إنشاء تمليح عشوائي لكلمات المرور لضمان أمان التخزين (سنتعلم المزيد عن التجزئة والتمليح في القسم التالي بمزيد من التفصيل). يجعل التمليح تجزئة كلمة المرور الضعيفة نسبياً أكثر تعقيدًا، مما يجعل من الصعب فك تشفيرها.

  3. سهولة الاستخدام – يوفر BcryptJS لمطوري JavaScript أداة لتشفير كلمات المرور دون الحاجة إلى فهم عميق لعمليات التجزئة._

في الخطوة التالية، سنتعرف بإيجاز على التجزئة و الملح.

كيف يعمل الهاش؟

قبل استخدام هذه المفاهيم في مشاريعك، يجب أن تفهم كيفية عمل التجزئة والملح.

التجزئة

التجزئة هي تحويل سلسلة بسيطة أو نص واضح إلى سلسلة من الأحرف العشوائية (تشفير). يتيح ذلك لتخزين آمن و/أو نقل البيانات الحساسة. تشمل التجزئة الخطوات الرئيسية التالية:

إدخال البيانات – أولاً، يتم تخزين البيانات التي قد تكون من أي نوع (ثنائية، حرف، عشري، إلخ.) كنص عادي أو سلسلة.

وظيفة التجزئة – وظيفة التجزئة هي خوارزمية رياضية تأخذ إدخال البيانات وتحولها إلى مجموعة من الأحرف أو رموز التجزئة. تكون وظائف التجزئة حاسمة (تنتج نفس الإخراج لنفس الإدخال) وتعتبر وظائفًا لاعودة (وهذا يعني أنه من المستحيل تقريبًا عكس هندسة الإخراج من وظائف التجزئة، أي تحويل التجزئة إلى بيانات الإدخال).

مقاومة التصادم – يعني ذلك أن تم إنشاء وظيفة تجزئة بالأخذ في الاعتبار فكرة مقاومة التصادم، أي أنه لا يمكن أن تحصل مخرجات متماثلة (شفرة تجزئة) لمدخلين مختلفين.

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

التمليح

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

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

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

  1. Password = ‘sammy’
  2. Hash = £%$^&£!23!3%!!
  3. Salt = 2vqw£4Df$%sdfk
  4. Hash + Salt = £%$^&£!23!3%!!2vqw£4Df$%sdfk

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

الآن، أنت جاهز لاستخدام BcryptJS وتأمين كلمات المرور بطريقة معتمدة في الصناعة.

تثبيت BcryptJS والوحدات الأخرى المطلوبة

الآن بعد أن تعرفت على الهاش والملح، كل ما تبقى لك هو الحصول على جهاز الكمبيوتر الخاص بك والبدء في البرمجة. سيكون هيكل المشروع كما يلي:

Project Structure

أولاً، سنبدأ بإنشاء مشروع npm بالخطوات التالية:

  1. افتح مجلدًا وأنشئ ملفًا، app.js.

  2. افتح نافذة الطرفية في هذا المجلد واكتب الأمر

npm init

بعد ذلك، سيُطلب منك إدخال البيانات لكن يمكنك الضغط على Enter دون تقديم أي بيانات.

  1. ثم، قم بإنشاء 3 ملفات أخرى، وهي
  • auth.js
  • db.js
  • User.js
  1. في نافذة الطرفية نفسها، اكتب الأمر التالي لتثبيت الحزم اللازمة.
npm install express mongoose BcryptJS nodemon

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

إعداد خادم مع Express JS

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

الخطوة 1 – إنشاء اتصال بقاعدة بيانات MongoDB

للاتصال بـ mongoDB، نستخدم النسخة المجتمعية. للحفاظ على تنظيم المشروع، ستحفظ الكود الخاص بإعداد الاتصال في ملف db.js.

  1. const mongoose = require("mongoose");
  2. const mongoURI = "mongodb://127.0.0.1:27017/bcrypt_database";
  3. const connectMongo = async () => {
  4. try {
  5. await mongoose.connect(mongoURI);
  6. console.log("Connected to MongoDB!");
  7. } catch (error) {
  8. console.error("Error connecting to MongoDB: ", error.message);
  9. }
  10. };
  11. module.exports = connectMongo;

هنا، تقوم ب استيراد حزمة mongoose، التي توفر واجهة برمجة تطبيقات للاتصال بين جافا سكريبت و MongoDB. أيضًا، في هذه الحالة، عنوان URI للاتصال يكون للتثبيت المحلي لـ mongoDB، إذا كنت تستخدم قاعدة بيانات سحابية (مثل Atlas)، فقط قم بتغيير عنوان URI إلى عنوان قاعدة بياناتك الخاصة.

معلومات
المُعرّف العالمي الموحّد (URI) مشابه لعنوان URL للخادم مع الفرق أن المُعرّف العالمي الموحّد يمكن أن يحدد اسم وهوية الموارد وموقعها على الإنترنت، بينما يُعتبر عنوان URL جزءًا فرعيًا من المُعرّف العالمي الموحّد قادرًا على القيام بالعمليات الأخيرة فقط.

الدالة connectToMongo هي دالة async لأن mongoose.connect تُرجع وعدًا من جافا سكريبت. هذه الدالة ستُعيد اتصالًا بنجاح عند تنفيذها بنجاح. في حال عدم ذلك، ستُعيد خطأً.

أخيرًا، نستخدم module.exports لتصدير هذه الدالة كلما تم استيراد وحدة db.js.

الخطوة ٢ – إنشاء بنية للمستخدم

ستحتاج إلى بنية أساسية لإنشاء أو مصادقة المستخدمين باستخدام قاعدة بيانات MongoDB. إذا لم تكن تعرف ما هي البنية، يمكنك استخدام دليل DO الممتاز هذا لـ فهم وإنشاء البنية في MongoDB. سنستخدم فقط حقلين لبنيتنا، email و password.

استخدم الكود التالي في وحدة User.js الخاصة بك.

  1. const mongoose = require("mongoose");
  2. const UserSchema = new mongoose.Schema({
  3. email:{
  4. type:String,
  5. required:true,
  6. unique:true
  7. },
  8. password:{
  9. type:String,
  10. required:true
  11. }
  12. });
  13. module.exports = mongoose.model('user', UserSchema)

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

وأخيرًا، نقوم بتصدير نموذج يسمى users. يُمثِّل النموذج نموذجًا في نهاية قاعدة البيانات. يمكنك أن تفكر في النموذج كقاعدة لتعريف نموذج، بينما يتم تخزين النموذج كمجموعة في قاعدة بيانات MongoDB.

يمكنك تحويل نموذج إلى نموذج باستخدام وظيفة model() من مكتبة mongoose.

الخطوة 3 – إعداد الخادم في ملف app.js

بعد اتباع الخطوات السابقة، لقد قمت بإنشاء نموذج بنجاح ووحدة لإنشاء اتصال بقاعدة بيانات MongoDB. الآن، ستتعلم كيفية إعداد الخادم. استخدم الكود التالي في ملف app.js الخاص بك.

  1. const connectToMongo = require("./db");
  2. const express = require("express");
  3. const app = express();
  4. connectToMongo();
  5. app.use(express.json());
  6. app.use("/auth", require("./auth"));
  7. const port = 3300;
  8. app.listen(port, () => {
  9. console.log(`Listening at http://localhost:${port}`);
  10. });

هنا، نقوم باستيراد الوحدات express و db.js للاتصال بـ MongoDB. ثم، نستخدم وسيط express.json() لمعالجة الردود JSON. يتم إنشاء المسارات في وحدة مختلفة (auth.js) للحفاظ على تنظيم ونظافة الكود. ثم، في النهاية، ننشئ نقطة انتهاء للخادم للاستماع على المنفذ 3300 على localhost. (يمكنك استخدام أي منفذ تفضله)

تشفير كلمات المرور وتخزينها في قاعدة بيانات MongoDB

حتى هذه النقطة، الخادم جاهز تقريبًا، والآن ستقوم بإنشاء نقاط نهاية للخادم. سنقوم بإنشاء نقطتين نهاية – signup و login. في نقطة النهاية للتسجيل، سنأخذ البريد الإلكتروني وكلمة المرور من مستخدم جديد ونقوم بتخزينها مع التشفير لكلمة المرور باستخدام BcryptJS.

في ملف auth.js، اكتب الكود التالي:

  1. const express = require("express");
  2. const router = express.Router();
  3. const User = require("./User");
  4. const bcrypt = require("bcryptjs");
  5. // الطريق 1:
  6. router.post("/signup", async (req, res) => {
  7. const salt = await bcrypt.genSalt(10);
  8. const secPass = await bcrypt.hash(req.body.password, salt);
  9. let user = await User.create({
  10. email: req.body.email,
  11. password: secPass,
  12. });
  13. res.json({ user });
  14. });
  15. module.exports = router;

نقوم بإجراء الواردات الضرورية ثم نقوم بإعداد موجّه express لإنشاء نقطة نهاية /signup. نستخدم الطريقة POST بحيث لا تتم الكشف عن بيانات الاعتماد في عنوان URL في التطبيق. بعد ذلك، نقوم بإنشاء ملح باستخدام وظيفة genSalt في حزمة scripts؛ يحتوي المعامل الذي يتم تمريره إلى دوال genSalt() على طول حرف الملح. ثم نستخدم وظيفة hash() في BcryptJS، التي تأخذ معلمة مطلوبة، سلسلة كلمة المرور التي سيتم تحويلها إلى رمز التجزئة، ومعامل اختياري، سلسلة الملح. ثم يعيد هذا الأمر تجزئة تحتوي على password و salt.

بعد ذلك، نستخدم وظيفة create() من وحدة التحكم mongoose لإنشاء وثيقة في قاعدة البيانات الخاصة بنا المحددة بقواعد نموذج users. تأخذ هذه الوظيفة كائنًا Javascript يحتوي على البريد الإلكتروني وكلمة المرور، ولكن بدلاً من تقديمها كسلسلة نصية نقدم secPass (كلمة المرور المشفرة + الملح) ليتم تخزينها في قاعدة البيانات. بهذه الطريقة، لقد قمنا بتخزين كلمة مرور بشكل آمن في قاعدة البيانات باستخدام تشفيرها مع ملح بدلاً من سلسلة نصية عادية. في النهاية، نقوم بإرجاع استجابة JSON تحتوي على نموذج المستخدم. (هذه الطريقة لإرسال الاستجابات هي فقط لمرحلة development؛ في الإنتاج، ستقوم بتبديل هذا برمز مصادقة أو شيء آخر).

لاختبار نقطة النهاية هذه، يجب عليك تشغيل الخادم أولاً، والذي يمكن القيام به عن طريق كتابة الأمر التالي في الطرفية.

cd <path to your project folder>
nodemon ./app.js

سيرفر على 3300 (أو أي منفذ تحدده). بعد ذلك، يمكنك إرسال طلب HTTP إلى عنوان URL http://localhost:3300/auth/signup بالجسم التالي:

  1. {
  2. "email":"[email protected]",
  3. "password":"sammy"
  4. }

سينتج عن ذلك الإخراج/الاستجابة التالية:

{
  "user": {
    "email": "[email protected]",
    "password": "$2a$10$JBka/WyJD0ohkzyu5Wu.JeCqQm33UIx/1xqIeNJ1AQI9kYZ0Gr0IS",
    "_id": "654510cd8f1edaa59a8bb589",
    "__v": 0
  }
}

ملاحظة: لن يكون تشفير كلمة المرور والمعرف متطابقين، حيث أنهما دائمًا فريدين.

في القسم التالي، ستتعلم كيفية الوصول إلى التشفير المخزن لكلمة المرور والتحقق منه مع كلمة مرور تقدم عند تسجيل الدخول/المصادقة.

الوصول إلى كلمة المرور المشفرة واستخدامها للمصادقة

حتى الآن، لقد تعلمت عن BcryptJS، التجزئة، التمليح، وتطوير خادم Express الذي يقوم بإنشاء مستخدمين جدد مع كلمات مرور مخزنة كتجزئة. الآن، ستتعلم كيفية استخدام كلمة المرور المخزنة والمصادقة على المستخدم عند محاولة تسجيل الدخول إلى التطبيق.

لإضافة طريقة المصادقة، قم بإلحاق المسار التالي في ملف auth.js بعد مسار /signup:

  1. // المسار 2:
  2. router.post("/login", async (req, res) => {
  3. let user = await User.findOne({ email: req.body.email });
  4. if (!user) {
  5. return res.status(400).json({ error: "Login with proper credentials!" });
  6. }
  7. const passwordCompare = await bcrypt.compare(req.body.password, user.password);
  8. if (!passwordCompare) {
  9. return res
  10. .status(400)
  11. .json({ error: "Login with proper credentials!" });
  12. }
  13. res.json({ success: "Authenticated!" });
  14. });

هنا، نستخدم الطريق الفرعي /auth/login لأداء مصادقة تسجيل الدخول لمستخدم موجود بالفعل. على غرار نقطة النهاية /auth/signup، سيكون هذا وظيفة async-await حيث يعيد BcryptJS الوعود.

أولاً، نستخدم وظيفة findOne من مكتبة Mongoose، التي تُستخدم للبحث عن وثيقة في مجموعة بناءً على استعلام بحث معين. في هذا السياق، نبحث عن المستخدم بناءً على البريد الإلكتروني. إذا لم يكن هناك مستخدم بالبريد الإلكتروني المقدم، فسيتم إرسال استجابة برمز الحالة 400 لعدم صحة البيانات الاعتماد. (ليس من الممارسات الجيدة تقديم معلومات حول أي معلمة غير صحيحة أثناء تسجيل الدخول، حيث يمكن للمهاجمين استخدام تلك المعلومات للعثور على حسابات قائمة).

إذا كان المستخدم الذي يمتلك البريد الإلكتروني المُقدم موجودًا، يتم نقل البرنامج إلى مقارنة كلمة المرور. لهذا الغرض، يوفر BcryptJS الطريقة compare() التي تأخذ سلسلة نصية غير مشفرة كمُعامل أول و hash (مع أو بدون salt) كمُعامل ثانٍ. ثم، يُرجع وعدًا من نوع Boolean؛ true إذا كانت كلمة المرور تُطابق الهاش و false إذا لم تكن تُطابق. بعد ذلك، يمكنك إضافة فحص بسيط باستخدام جملة if وإرجاع النجاح أو الخطأ استنادًا إلى المقارنة.

وأخيرًا، ستقوم بتصدير express router باستخدام module.exports لنقطة بداية app.js لاستخدامه في المسارات.

للاختبار لهذا المسار، يمكنك إرسال استجابة أخرى من نوع HTTP إلى هذا الرابط http://localhost:3300/auth/login مع الجسم على النحو التالي:

  1. {
  2. "email":"[email protected]",
  3. "password":"sammy"
  4. }

سيعطي هذا الطلب الاستجابة التالية:

{
  "success": "Authenticated!"
}

وأخيرًا، سيبدو auth.js كالتالي:

  1. const express = require("express");
  2. const router = express.Router();
  3. const User = require("./User");
  4. const bcrypt = require("bcryptjs");
  5. // المسار 1:
  6. router.post("/signup", async (req, res) => {
  7. const salt = await bcrypt.genSalt(10);
  8. const secPass = await bcrypt.hash(req.body.password, salt);
  9. let user = await User.create({
  10. email: req.body.email,
  11. password: secPass,
  12. });
  13. res.json({ user });
  14. });
  15. // المسار 2:
  16. router.post("/login", async (req, res) => {
  17. let user = await User.findOne({ email: req.body.email });
  18. if (!user) {
  19. return res.status(400).json({ error: "Login with proper credentials!" });
  20. }
  21. const passwordCompare = await bcrypt.compare(req.body.password, user.password);
  22. if (!passwordCompare) {
  23. return res
  24. .status(400)
  25. .json({ error: "Login with proper credentials!" });
  26. }
  27. res.json({ success: "Authenticated!" });
  28. });
  29. module.exports = router;

الاستنتاج

تم إنشاء هذا البرنامج التعليمي لإنشاء خادم لشرح استخدام BcryptJS لتخزين كلمات مرور قاعدة البيانات بشكل آمن والوصول إليها. يمكنك المضي قدمًا في ذلك بواسطة:

  • تنفيذ مسارات إضافية لمهام أخرى مثل استرجاع بيانات اعتماد المستخدم، تحديث بيانات اعتماد المستخدم، إلخ.

  • تنفيذ رموز المصادقة لإرسالها كاستجابات ، الخ.

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

Source:
https://www.digitalocean.com/community/tutorials/how-to-handle-passwords-safely-with-bcryptsjs-in-javascript