Node.js و Express

المصادقة باستخدام Passport.js

20 دقيقة الدرس 15 من 40

المصادقة باستخدام Passport.js

Passport.js هي البرمجية الوسيطة الأكثر شعبية للمصادقة في Node.js، وتوفر نهجًا مرنًا ونمطيًا للمصادقة. مع أكثر من 500+ استراتيجية، تبسط Passport تنفيذ طرق المصادقة المختلفة في تطبيقات Express.

نظرة عامة على Passport.js

توفر Passport طريقة نظيفة ومنظمة للتعامل مع المصادقة بهذه الميزات الرئيسية:

  • قائم على الاستراتيجيات: استراتيجيات مصادقة قابلة للتوصيل (Local, OAuth, JWT، إلخ)
  • مدفوع بالبرمجيات الوسيطة: يتكامل بسلاسة مع Express
  • دعم الجلسات: إدارة جلسات مدمجة مع التسلسل
  • قابل للتوسع: سهولة إنشاء استراتيجيات مخصصة
  • المجتمع: نظام بيئي كبير مع 500+ استراتيجية مصادقة
مفهوم رئيسي: تستخدم Passport "استراتيجيات" لمصادقة المستخدمين. كل استراتيجية تتعامل مع طريقة مصادقة محددة (اسم المستخدم/كلمة المرور، OAuth، JWT، إلخ).

استراتيجيات المصادقة

تدعم Passport العديد من استراتيجيات المصادقة:

// استراتيجيات Passport الشائعة // 1. الاستراتيجية المحلية (اسم المستخدم/كلمة المرور) passport-local // 2. استراتيجية JWT (رموز ويب JSON) passport-jwt // 3. استراتيجيات OAuth passport-google-oauth20 passport-facebook passport-twitter passport-github // 4. استراتيجيات أخرى passport-oauth2 passport-saml passport-openid passport-ldap

تثبيت Passport

تثبيت Passport والاستراتيجيات المطلوبة:

# حزمة Passport الأساسية npm install passport # للمصادقة المحلية باسم المستخدم/كلمة المرور npm install passport-local bcryptjs # لمصادقة JWT npm install passport-jwt jsonwebtoken # لدعم الجلسات npm install express-session

تكوين Passport - الاستراتيجية المحلية

إعداد Passport بمصادقة اسم المستخدم/كلمة المرور:

// config/passport.js const passport = require('passport'); const LocalStrategy = require('passport-local').Strategy; const bcrypt = require('bcryptjs'); const User = require('../models/User'); // تكوين الاستراتيجية المحلية passport.use(new LocalStrategy( { usernameField: 'email', // استخدام البريد الإلكتروني بدلاً من اسم المستخدم passwordField: 'password' }, async (email, password, done) => { try { // البحث عن المستخدم بالبريد الإلكتروني const user = await User.findOne({ email }).select('+password'); // التحقق من وجود المستخدم if (!user) { return done(null, false, { message: 'البريد الإلكتروني أو كلمة المرور غير صحيحة' }); } // التحقق من كلمة المرور const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return done(null, false, { message: 'البريد الإلكتروني أو كلمة المرور غير صحيحة' }); } // المصادقة ناجحة return done(null, user); } catch (error) { return done(error); } } )); module.exports = passport;

التسلسل وإلغاء التسلسل للمستخدم

تكوين كيفية تخزين بيانات المستخدم واسترجاعها من الجلسات:

// config/passport.js (تتمة) // تسلسل المستخدم - تخزين معرف المستخدم في الجلسة passport.serializeUser((user, done) => { done(null, user.id); }); // إلغاء تسلسل المستخدم - استرجاع المستخدم من قاعدة البيانات باستخدام المعرف passport.deserializeUser(async (id, done) => { try { const user = await User.findById(id).select('-password'); done(null, user); } catch (error) { done(error); } });
إدارة الجلسات: يخزن serializeUser معرف المستخدم فقط في الجلسة للحفاظ على خفتها. يسترجع deserializeUser كائن المستخدم الكامل في الطلبات اللاحقة.

التكامل مع Express

إعداد Express مع Passport والجلسات:

// app.js const express = require('express'); const session = require('express-session'); const passport = require('./config/passport'); const app = express(); // محلل Body app.use(express.json()); app.use(express.urlencoded({ extended: true })); // تكوين الجلسة app.use(session({ secret: process.env.SESSION_SECRET || 'your-secret-key', resave: false, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV === 'production', // HTTPS فقط في الإنتاج httpOnly: true, maxAge: 24 * 60 * 60 * 1000 // 24 ساعة } })); // تهيئة Passport app.use(passport.initialize()); app.use(passport.session()); // المسارات app.use('/api/auth', require('./routes/auth')); module.exports = app;

مسارات المصادقة القائمة على الجلسات

تنفيذ مسارات تسجيل الدخول والتسجيل وتسجيل الخروج:

// routes/auth.js const express = require('express'); const router = express.Router(); const passport = require('passport'); const bcrypt = require('bcryptjs'); const User = require('../models/User'); // تسجيل مستخدم جديد router.post('/register', async (req, res) => { try { const { name, email, password } = req.body; // التحقق من وجود المستخدم const existingUser = await User.findOne({ email }); if (existingUser) { return res.status(400).json({ success: false, message: 'المستخدم موجود بالفعل' }); } // تشفير كلمة المرور const hashedPassword = await bcrypt.hash(password, 10); // إنشاء المستخدم const user = await User.create({ name, email, password: hashedPassword }); // تسجيل الدخول التلقائي بعد التسجيل req.login(user, (err) => { if (err) { return res.status(500).json({ success: false, message: 'التسجيل ناجح ولكن فشل تسجيل الدخول' }); } res.status(201).json({ success: true, user: { id: user._id, name: user.name, email: user.email } }); }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // تسجيل الدخول باستخدام استراتيجية Passport المحلية router.post('/login', (req, res, next) => { passport.authenticate('local', (err, user, info) => { if (err) { return res.status(500).json({ success: false, message: err.message }); } if (!user) { return res.status(401).json({ success: false, message: info.message || 'فشلت المصادقة' }); } // إنشاء جلسة req.login(user, (err) => { if (err) { return res.status(500).json({ success: false, message: 'فشل تسجيل الدخول' }); } res.json({ success: true, user: { id: user._id, name: user.name, email: user.email } }); }); })(req, res, next); }); // تسجيل الخروج router.post('/logout', (req, res) => { req.logout((err) => { if (err) { return res.status(500).json({ success: false, message: 'فشل تسجيل الخروج' }); } res.json({ success: true, message: 'تم تسجيل الخروج بنجاح' }); }); }); // الحصول على المستخدم الحالي router.get('/me', (req, res) => { if (!req.isAuthenticated()) { return res.status(401).json({ success: false, message: 'غير مصادق عليه' }); } res.json({ success: true, user: req.user }); }); module.exports = router;

برمجية وسيطة للمصادقة

إنشاء برمجية وسيطة لحماية المسارات:

// middleware/auth.js // التحقق من مصادقة المستخدم exports.isAuthenticated = (req, res, next) => { if (req.isAuthenticated()) { return next(); } res.status(401).json({ success: false, message: 'يرجى تسجيل الدخول للوصول إلى هذا المورد' }); }; // التحقق من دور محدد للمستخدم exports.authorizeRoles = (...roles) => { return (req, res, next) => { if (!req.isAuthenticated()) { return res.status(401).json({ success: false, message: 'يرجى تسجيل الدخول أولاً' }); } if (!roles.includes(req.user.role)) { return res.status(403).json({ success: false, message: `الدور ${req.user.role} غير مسموح له بالوصول إلى هذا المورد` }); } next(); }; }; // الاستخدام في المسارات // router.get('/profile', isAuthenticated, getProfile); // router.delete('/admin/users/:id', authorizeRoles('admin'), deleteUser);

استراتيجية Passport JWT

تنفيذ مصادقة JWT عديمة الحالة مع Passport:

// config/passport-jwt.js const passport = require('passport'); const JwtStrategy = require('passport-jwt').Strategy; const ExtractJwt = require('passport-jwt').ExtractJwt; const User = require('../models/User'); const opts = { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_SECRET || 'your-jwt-secret' }; passport.use(new JwtStrategy(opts, async (jwt_payload, done) => { try { // البحث عن المستخدم بالمعرف من حمولة JWT const user = await User.findById(jwt_payload.userId); if (user) { return done(null, user); } return done(null, false); } catch (error) { return done(error, false); } })); module.exports = passport;

مسارات JWT مع Passport

// routes/auth-jwt.js const express = require('express'); const router = express.Router(); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const passport = require('../config/passport-jwt'); const User = require('../models/User'); // إنشاء رمز JWT const generateToken = (user) => { return jwt.sign( { userId: user._id, email: user.email }, process.env.JWT_SECRET || 'your-jwt-secret', { expiresIn: '7d' } ); }; // تسجيل الدخول مع JWT router.post('/login', async (req, res) => { try { const { email, password } = req.body; const user = await User.findOne({ email }).select('+password'); if (!user || !(await bcrypt.compare(password, user.password))) { return res.status(401).json({ success: false, message: 'بيانات الاعتماد غير صالحة' }); } const token = generateToken(user); res.json({ success: true, token, user: { id: user._id, name: user.name, email: user.email } }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // مسار محمي باستخدام Passport JWT router.get('/profile', passport.authenticate('jwt', { session: false }), (req, res) => { res.json({ success: true, user: req.user }); } ); module.exports = router;

OAuth مع Passport (مثال Google)

تنفيذ تسجيل الدخول الاجتماعي مع Google OAuth:

// npm install passport-google-oauth20 // config/passport-google.js const passport = require('passport'); const GoogleStrategy = require('passport-google-oauth20').Strategy; const User = require('../models/User'); passport.use(new GoogleStrategy({ clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, callbackURL: '/api/auth/google/callback' }, async (accessToken, refreshToken, profile, done) => { try { // التحقق من وجود المستخدم let user = await User.findOne({ googleId: profile.id }); if (user) { return done(null, user); } // إنشاء مستخدم جديد user = await User.create({ googleId: profile.id, name: profile.displayName, email: profile.emails[0].value, avatar: profile.photos[0].value }); done(null, user); } catch (error) { done(error, null); } })); // routes/auth-oauth.js router.get('/google', passport.authenticate('google', { scope: ['profile', 'email'] }) ); router.get('/google/callback', passport.authenticate('google', { failureRedirect: '/login', session: false }), (req, res) => { // إنشاء رمز JWT const token = generateToken(req.user); // إعادة التوجيه إلى الواجهة الأمامية مع الرمز res.redirect(`${process.env.FRONTEND_URL}/auth/success?token=${token}`); } );
إعداد OAuth: تحتاج إلى تسجيل تطبيقك مع Google OAuth Console للحصول على CLIENT_ID و CLIENT_SECRET. قم بإعداد عناوين URL المعتمدة للتوجيه بشكل صحيح.

إعداد Passport الكامل

// app.js - التكوين الكامل const express = require('express'); const session = require('express-session'); const MongoStore = require('connect-mongo'); // استيراد تكوينات passport require('./config/passport'); // الاستراتيجية المحلية require('./config/passport-jwt'); // استراتيجية JWT require('./config/passport-google'); // Google OAuth const passport = require('passport'); const app = express(); // البرمجيات الوسيطة app.use(express.json()); app.use(express.urlencoded({ extended: true })); // الجلسة مع مخزن MongoDB app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, store: MongoStore.create({ mongoUrl: process.env.MONGODB_URI, touchAfter: 24 * 3600 // تحديث الجلسة الكسول }), cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, maxAge: 1000 * 60 * 60 * 24 * 7 // 7 أيام } })); // تهيئة Passport app.use(passport.initialize()); app.use(passport.session()); // المسارات app.use('/api/auth', require('./routes/auth')); app.use('/api/auth-jwt', require('./routes/auth-jwt')); app.use('/api/auth-oauth', require('./routes/auth-oauth')); module.exports = app;
مخزن الجلسات: للإنتاج، استخدم دائمًا مخزن جلسات مناسب (MongoDB، Redis، PostgreSQL) بدلاً من مخزن الذاكرة الافتراضي، والذي لا يتوسع ويفقد البيانات عند إعادة تشغيل الخادم.

تمرين عملي

ابنِ نظام مصادقة كامل باستخدام Passport.js:

  1. إعداد Passport مع الاستراتيجية المحلية لمصادقة البريد الإلكتروني/كلمة المرور
  2. تنفيذ تسجيل المستخدم مع تشفير كلمة المرور
  3. إنشاء نقاط نهاية تسجيل الدخول وتسجيل الخروج
  4. تكوين إدارة الجلسات مع مخزن MongoDB
  5. إضافة استراتيجية JWT لمصادقة API عديمة الحالة
  6. تنفيذ مسارات محمية مع برمجية وسيطة للمصادقة
  7. إضافة تفويض قائم على الأدوار (مستخدم، مسؤول)
  8. إضافي: تنفيذ تسجيل الدخول عبر Google OAuth