التحقق من كلمة المرور
جزء من قسم المصادقة والأمان في رحلة Node.js على Coddy. الدرس 6 من 24.
يشتق تسجيل الدخول مفتاحًا من كلمة المرور المُدخلة باستخدام الملح المحفوظ وطول المفتاح الأصلي. سيؤدي استخدام ملح جديد إلى مفتاح مختلف، حتى عند إدخال كلمة المرور الصحيحة. لا تُفكّ كلمة المرور الأصلية من التخزين مطلقًا.
// تم تخزين رمز دعوة بصيغة salt.keyHex
const [salt, keyHex] = savedInvite.split('.');
const expected = Buffer.from(keyHex, 'hex');
const attempted = await scryptAsync(inviteCode, salt, 32);قارن تسلسلي البايتات الناتجين باستخدام timingSafeEqual، بعد التأكد من تطابق طولهما. فهذا يتجنب التوقف المبكر المعتمد على المحتوى، والذي قد يكشف معلومات عبر التوقيت. يتحقق === مع Buffer من الهوية، ولا يضمن إجراء مقارنة السلاسل النصية العادية بزمن ثابت. تستخدم دالة كلمة المرور لديك الفاصل النقطي في سجل كلمة المرور ومفتاحًا بطول 64 بايتًا. يستخدم المسار POST /login الذي تضيفه النتيجة لإرجاع 200 أو 401.
التحدي
سهلتفتح هذه الخطوة مشروع واجهة API للملاحظات الخاصة المُعدّ مسبقًا في الدورة، بدلًا من ملفات مشروعك من درس لا تخزّن كلمات المرور أبدًا (بما فيها passwords.js). يظل عملك السابق في درسه الخاص. اقرأ الملفات المقدّمة قبل تعديل passwords.js. تحتوي هذه النسخة على دوال مساعدة لتجزئة كلمات المرور ومسارات تصحيح الأخطاء المقدّمة.
يحفظ التسجيل الآن تجزئات مملّحة. أضف تسجيل دخول يتحقق من كلمة المرور بمقارنتها بما حفظه التسجيل.
- في
passwords.js، أضف دالةasyncباسمverifyPassword(password, stored)وصدّرها.storedقيمة حفظها التسجيل، مثلf2bb...f065:c3f7...dbd2. أعدtrueعندما تكونpasswordهي كلمة المرور التي أنشأت هذه القيمة، وfalseخلاف ذلك:- خذ الملح من الجزء الذي يسبق النقطتين.
- جزّئ
passwordباستخدام ذلك الملح إلى مفتاح طوله 64 بايت، بالطريقة نفسها التي تستخدمهاhashPassword. - فك ترميز المفتاح المحفوظ بعد النقطتين من النظام الست عشري. إذا اختلف طول المفتاحين بالبايت، فأعد
falseمن دون استدعاءtimingSafeEqual، كما في مقارنة بايتات المفاتيح. وإلا فأعد نتيجة المقارنة.
- في
server.js، استوردverifyPasswordبجوارhashPassword. أضفPOST /login. يستقبل هذا المسار جسم الطلب نفسه الذي يستقبله التسجيل، مثل{"username": "ada", "password": "correct horse"}.- اعثر على المستخدم الذي يحمل اسم المستخدم هذا. الدالة
verifyPasswordبطيئة وتعيد وعدًا، لذا انتظر نتيجتها. - عندما يكون المستخدم موجودًا وكلمة المرور صحيحة، أرسل استجابة بالحالة 200 و
{"username": "ada"}. - وإلا، فأرسل استجابة بالحالة 401 و
{"error": "Wrong username or password"}. تعني 401 غير مصرّح به أن الطلب لم يثبت هوية صاحب الطلب. - أرسل استجابة 401 نفسها تمامًا عند إدخال كلمة مرور خاطئة وعند استخدام اسم مستخدم لم يسجّل أحد به. عندئذٍ، لن يكشف نص الاستجابة أبدًا لأي شخص أسماء المستخدمين الموجودة.
- اعثر على المستخدم الذي يحمل اسم المستخدم هذا. الدالة
جرّب ذلك: اضغط على «العب». أرسل POST /signup مع {"username": "ada", "password": "correct horse"}: الحالة 201. أرسل POST /login مع الجسم نفسه: الحالة 200 و{"username": "ada"}. أرسل POST /login مع {"username": "ada", "password": "wrong horse"}: الحالة 401 و{"error": "Wrong username or password"}. أرسل POST /login مع {"username": "nobody", "password": "correct horse"}: ستظهر استجابة 401 نفسها.
جرّب بنفسك
import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';
const scryptAsync = promisify(scrypt);
// يحوّل كلمة المرور إلى النص الذي تخزّنه signup.
export async function hashPassword(password) {
const salt = randomBytes(16).toString('hex');
const key = await scryptAsync(password, salt, 64);
return `${salt}:${key.toString('hex')}`;
}
جميع دروس المصادقة والأمان
1كلمات المرور
البايتات والنص الست عشرياشتقاق مفتاح كلمة المرورقيمة Salt جديدةلا تخزّن كلمات المرور أبدًامقارنة بايتات المفتاحالتحقق من كلمة المرورقواعد التسجيلتدرّب بنفسك: Playground لـ Express