يقول مؤلف المقال ، الذي ننشر ترجمته اليوم ، إنه يمكنك الآن ملاحظة الشعبية المتزايدة لخدمات المصادقة مثل مصادقة Google Firebase و AWS Cognito و Auth0. أصبحت الحلول العامة مثل passport.js هي المعيار الصناعي. ولكن ، بالنظر إلى الوضع الحالي ، أصبح من الشائع أن المطورين لا يفهمون تمامًا الآليات التي تشارك في تشغيل أنظمة المصادقة.
هذه المادة مخصصة لمشكلة تنظيم مصادقة المستخدم في Node.js. في ذلك ، على سبيل المثال العملي ، يتم النظر في تنظيم تسجيل المستخدم في النظام وتنظيم دخولهم إلى النظام. سيثير مشاكل مثل العمل مع تقنية JWT وانتحال هوية المستخدم.

أيضًا ، انتبه إلى مستودع جيثب
هذا ، الذي يحتوي على رمز مشروع Node.js ، وبعض الأمثلة الواردة في هذه المقالة. يمكنك استخدام هذا المستودع كأساس للتجارب الخاصة بك.
متطلبات المشروع
فيما يلي متطلبات المشروع الذي سنتعامل معه هنا:
- وجود قاعدة بيانات يتم فيها تخزين عنوان البريد الإلكتروني وكلمة المرور للمستخدم ، إما clientId و clientSecret ، أو ما يشبه مزيج من المفاتيح الخاصة والعامة.
- استخدام خوارزمية تشفير قوية وفعالة لتشفير كلمة مرور.
في الوقت الذي أكتب فيه هذه المادة ، أعتقد أن أفضل خوارزميات التشفير الموجودة هي Argon2. أطلب منك عدم استخدام خوارزميات تشفير بسيطة مثل SHA256 أو SHA512 أو MD5.
بالإضافة إلى ذلك ، أقترح عليك إلقاء نظرة على
هذه المادة الرائعة ، حيث يمكنك العثور على تفاصيل حول اختيار خوارزمية لتجزئة كلمات المرور.
تسجيل المستخدمين في النظام
عند إنشاء مستخدم جديد في النظام ، يجب تجزئة كلمة المرور الخاصة به وتخزينها في قاعدة البيانات. يتم تخزين كلمة المرور في قاعدة البيانات إلى جانب عنوان البريد الإلكتروني والمعلومات الأخرى حول المستخدم (على سبيل المثال ، قد يكون ملف تعريف المستخدم ووقت التسجيل وما إلى ذلك).
import * as argon2 from 'argon2'; class AuthService { public async SignUp(email, password, name): Promise<any> { const passwordHashed = await argon2.hash(password); const userRecord = await UserModel.create({ password: passwordHashed, email, name, }); return {
يجب أن تبدو معلومات حساب المستخدم كما يلي.
تم استرداد بيانات المستخدم من MongoDB باستخدام Robo3Tتسجيل دخول المستخدم
فيما يلي رسم تخطيطي للإجراءات التي يتم تنفيذها عندما يحاول المستخدم تسجيل الدخول.
تسجيل دخول المستخدمإليك ما يحدث عندما يقوم المستخدم بتسجيل الدخول:
- يرسل العميل إلى الخادم مجموعة من المعرف العام والمفتاح الخاص للمستخدم. هذا هو عادة عنوان البريد الإلكتروني وكلمة المرور.
- يبحث الخادم عن المستخدم في قاعدة البيانات عن طريق عنوان البريد الإلكتروني.
- في حالة وجود المستخدم في قاعدة البيانات ، يقوم الخادم بتجزئة كلمة المرور المرسلة إليها ومقارنة ما حدث بتجزئة كلمة المرور المخزنة في قاعدة البيانات.
- في حالة نجاح عملية التحقق ، ينشئ الخادم رمزًا مميزًا للرمز المميز أو المصادقة - JSON Web Token (JWT).
JWT هو مفتاح مؤقت. يجب على العميل إرسال هذا المفتاح إلى الخادم مع كل طلب إلى نقطة النهاية المصادقة.
import * as argon2 from 'argon2'; class AuthService { public async Login(email, password): Promise<any> { const userRecord = await UserModel.findOne({ email }); if (!userRecord) { throw new Error('User not found') } else { const correctPassword = await argon2.verify(userRecord.password, password); if (!correctPassword) { throw new Error('Incorrect password') return { user: { email: userRecord.email, name: userRecord.name, }, token: this.generateJWT(userRecord), }
يتم تنفيذ التحقق من كلمة المرور باستخدام مكتبة argon2. هذا لمنع ما يسمى بـ "
الهجمات الزمنية ". عند تنفيذ مثل هذا الهجوم ، يحاول المهاجم تكسير كلمة المرور بالقوة الغاشمة ، بناءً على تحليل لمدى الوقت الذي يحتاجه الخادم لتكوين استجابة.
الآن دعونا نتحدث عن كيفية إنشاء JWT.
ما هو JWT؟
JSON Web Token (JWT) هو كائن JSON مشفر في شكل سلسلة. يمكن أن تؤخذ الرموز كبديل لملفات تعريف الارتباط ، التي لديها العديد من المزايا عليها.
يتكون الرمز من ثلاثة أجزاء. هذا هو العنوان ، الحمولة ، والتوقيع. يوضح الشكل التالي مظهره.
JWTيمكن فك تشفير البيانات المميزة من جانب العميل دون استخدام مفتاح سري أو توقيع سري.
قد يكون هذا مفيدًا لنقل ، على سبيل المثال ، البيانات الوصفية المشفرة داخل الرمز المميز. يمكن أن تصف هذه البيانات الوصفية دور المستخدم وملفه الشخصي ومدة الرمز المميز وما إلى ذلك. يمكن أن تكون مخصصة للاستخدام في التطبيقات الأمامية.
إليك ما يمكن أن يبدو عليه الرمز المميز.
رمز فك الشفرةتوليد JWT في Node.js
لنقم بإنشاء دالة
generateToken
التي نحتاجها لإكمال العمل على خدمة مصادقة المستخدم.
يمكنك إنشاء JWT باستخدام مكتبة jsonwebtoken. يمكنك العثور على هذه المكتبة في npm.
import * as jwt from 'jsonwebtoken' class AuthService { private generateToken(user) { const data = { _id: user._id, name: user.name, email: user.email }; const signature = 'MySuP3R_z3kr3t'; const expiration = '6h'; return jwt.sign({ data, }, signature, { expiresIn: expiration }); }
الشيء الأكثر أهمية هنا هو البيانات المشفرة. لا ترسل معلومات المستخدم السرية في الرموز.
التوقيع (هنا هو ثابت
signature
) هو البيانات السرية التي يتم استخدامها لإنشاء JWT. من المهم للغاية التأكد من أن التوقيع لا يقع في الأيدي الخطأ. إذا تم اختراق التوقيع ، فسيتمكن المهاجم من إنشاء الرموز المميزة نيابة عن المستخدمين وسرقة جلساتهم.
حماية نقطة النهاية والتحقق من صحة JWT
يحتاج رمز العميل الآن إلى إرسال JWT في كل طلب إلى نقطة نهاية آمنة.
يوصى بتضمين JWT في رؤوس الطلبات. وعادة ما يتم تضمينها في رأس التخويل.
رأس التفويضالآن ، على الخادم ، تحتاج إلى إنشاء رمز يمثل الوسيطة للطرق السريعة. ضع هذا الرمز في ملف
isAuth.ts
:
import * as jwt from 'express-jwt';
من المفيد أن تكون قادرًا على الحصول على معلومات كاملة حول حساب المستخدم من قاعدة البيانات وإرفاقها بالطلب. في حالتنا ، يتم تطبيق هذه الميزة باستخدام الوسيطة من ملف
attachCurrentUser.ts
. إليك الرمز المبسط:
export default (req, res, next) => { const decodedTokenData = req.tokenData; const userRecord = await UserModel.findOne({ _id: decodedTokenData._id }) req.currentUser = userRecord; if(!userRecord) { return res.status(401).end('User not found') } else { return next(); }
بعد تنفيذ هذه الآلية ، ستتمكن المسارات من تلقي معلومات حول المستخدم الذي ينفذ الطلب:
import isAuth from '../middlewares/isAuth'; import attachCurrentUser from '../middlewares/attachCurrentUser'; import ItemsModel from '../models/items'; export default (app) => { app.get('/inventory/personal-items', isAuth, attachCurrentUser, (req, res) => { const user = req.currentUser; const userItems = await ItemsModel.find({ owner: user._id }); return res.json(userItems).status(200); })
مسار
inventory/personal-items
محمي الآن. للوصول إليه ، يجب أن يكون لدى المستخدم JWT صالح. بالإضافة إلى ذلك ، يمكن للمسار استخدام معلومات المستخدم للبحث في قاعدة البيانات عن المعلومات التي يحتاجها.
لماذا الرموز محمية من المتسللين؟
بعد القراءة حول استخدام JWT ، قد تسأل نفسك السؤال التالي: "إذا كان يمكن فك تشفير بيانات JWT من جانب العميل ، فهل من الممكن معالجة الرمز المميز بطريقة لتغيير معرف المستخدم أو البيانات الأخرى؟".
فك الشفرة - العملية بسيطة جدا. ومع ذلك ، لا يمكنك "إعادة" هذا الرمز بدون وجود هذا التوقيع ، تلك البيانات السرية التي تم استخدامها عند توقيع JWT على الخادم.
هذا هو السبب في حماية هذه البيانات الحساسة مهم جدا.
يتحقق خادمنا من التوقيع في isAuth الوسيطة. مكتبة Express-jwt هي المسؤولة عن التحقق.
الآن ، بعد أن اكتشفنا كيف تعمل تقنية JWT ، دعونا نتحدث عن بعض الميزات الإضافية المثيرة التي توفرها لنا.
كيفية انتحال شخصية المستخدم؟
انتحال المستخدم هو أسلوب يستخدم لتسجيل الدخول إلى نظام كمستخدم معين دون معرفة كلمة المرور الخاصة به.
هذه الميزة مفيدة جدًا للمسؤولين الفائقين أو المطورين أو موظفي الدعم. يسمح انتحال الهوية بحل المشكلات التي تظهر فقط في سياق المستخدمين الذين يعملون مع النظام.
يمكنك العمل مع التطبيق نيابة عن المستخدم دون معرفة كلمة المرور الخاصة به. للقيام بذلك ، يكفي إنشاء JWT مع التوقيع الصحيح ومع البيانات الوصفية اللازمة التي تصف المستخدم.
قم بإنشاء نقطة نهاية يمكن أن تنشئ رموزًا لدخول النظام تحت ستار مستخدمين محددين. يمكن للمسؤول الفائق للنظام فقط استخدام نقطة النهاية هذه.
بالنسبة للمبتدئين ، نحتاج إلى تعيين دور لهذا المستخدم بمستوى امتياز أعلى من المستخدمين الآخرين. يمكن القيام بذلك بعدة طرق مختلفة. على سبيل المثال ، ببساطة إضافة حقل
role
إلى معلومات المستخدم المخزنة في قاعدة البيانات.
قد تبدو مثل تلك الموضحة أدناه.
حقل جديد في معلومات المستخدمقيمة حقل
role
super-admin
هي
super-admin
.
بعد ذلك ، تحتاج إلى إنشاء برنامج وسيط جديد يتحقق من دور المستخدم:
export default (requiredRole) => { return (req, res, next) => { if(req.currentUser.role === requiredRole) { return next(); } else { return res.status(401).send('Action not allowed'); }
يجب أن توضع بعد isAuth و attachCurrentUser. قم الآن بإنشاء نقطة النهاية التي تنشئ JWT للمستخدم نيابة عن المسؤول الفائق الذي يريد تسجيل الدخول:
import isAuth from '../middlewares/isAuth'; import attachCurrentUser from '../middlewares/attachCurrentUser'; import roleRequired from '../middlwares/roleRequired'; import UserModel from '../models/user'; export default (app) => { app.post('/auth/signin-as-user', isAuth, attachCurrentUser, roleRequired('super-admin'), (req, res) => { const userEmail = req.body.email; const userRecord = await UserModel.findOne({ email: userEmail }); if(!userRecord) { return res.status(404).send('User not found'); return res.json({ user: { email: userRecord.email, name: userRecord.name }, jwt: this.generateToken(userRecord) }) .status(200); })
كما ترون ، لا يوجد شيء غامض. يعرف المسؤول الفائق عنوان البريد الإلكتروني للمستخدم نيابةً عن الشخص الذي تريد تسجيل الدخول إليه. يذكرنا منطق الكود أعلاه بالكيفية التي يعمل بها الكود ، ويوفر مدخلاً لنظام المستخدمين العاديين. الفرق الرئيسي هو أن كلمة المرور غير محددة هنا.
لم يتم التحقق من كلمة المرور هنا نظرًا لأنه ببساطة غير مطلوب هنا. يتم توفير الأمن نقطة النهاية عن طريق الوسيطة.
النتائج
لا حرج في الاعتماد على خدمات مصادقة الجهات الخارجية والمكتبات. هذا يساعد المطورين على توفير الوقت. لكنهم بحاجة أيضًا إلى معرفة المبادئ التي يستند إليها تشغيل أنظمة المصادقة ، وما الذي يضمن تشغيل هذه الأنظمة.
في هذه المقالة ، استكشفنا إمكانيات مصادقة JWT ، وتحدثنا عن أهمية اختيار خوارزمية تشفير جيدة لتجزئة كلمات المرور. درسنا إنشاء آلية انتحال المستخدم.
القيام بنفس الشيء باستخدام شيء مثل passport.js ليس بالأمر السهل. المصادقة موضوع ضخم. ربما سنعود لها.
أعزائي القراء! كيف يمكنك إنشاء أنظمة المصادقة لمشاريع Node.js الخاصة بك؟