Node.js و Express
المصادقة باستخدام Passport.js
المصادقة باستخدام 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:
- إعداد Passport مع الاستراتيجية المحلية لمصادقة البريد الإلكتروني/كلمة المرور
- تنفيذ تسجيل المستخدم مع تشفير كلمة المرور
- إنشاء نقاط نهاية تسجيل الدخول وتسجيل الخروج
- تكوين إدارة الجلسات مع مخزن MongoDB
- إضافة استراتيجية JWT لمصادقة API عديمة الحالة
- تنفيذ مسارات محمية مع برمجية وسيطة للمصادقة
- إضافة تفويض قائم على الأدوار (مستخدم، مسؤول)
- إضافي: تنفيذ تسجيل الدخول عبر Google OAuth