كيفية توثيق المستخدمين وتطبيق CORS في تطبيقات Node.js
مقدمة
يُعد توثيق المستخدمين من أهم الجوانب الأمنية في أي تطبيق حديث، خاصة عند بناء واجهات برمجية باستخدام Node.js. فمجرد إنشاء نقاط وصول API دون حماية كافية قد يعرّض بيانات المستخدمين والأنظمة الداخلية لمخاطر متعددة. في هذا الدليل العملي، ستتعرف على طريقة إنشاء نظام تسجيل وتسجيل دخول، وتأمين المسارات باستخدام JWT، ثم تفعيل CORS بشكل صحيح للسماح بالاتصال بين التطبيقات المختلفة ضمن ضوابط واضحة.

المتطلبات الأساسية قبل البدء
للاستفادة الكاملة من هذا الشرح، يُفضّل أن تكون لديك معرفة عملية بالأساسيات التالية:
- فهم جيد للغة
JavaScript. - إلمام بأساسيات العمل مع
Node.js. - معرفة مناسبة بقواعد البيانات مثل
MongoDB. - استخدام أداة
Postmanلاختبار نقاط الوصول.
ما المقصود بالمصادقة والتفويض؟
المصادقة Authentication
المصادقة هي عملية التحقق من هوية المستخدم. بمعنى آخر، يطلب النظام من المستخدم بيانات اعتماد مثل البريد الإلكتروني وكلمة المرور، ثم يتحقق من صحتها. إذا كانت صحيحة، يعتبر المستخدم معروف الهوية داخل النظام.
التفويض Authorization
أما التفويض فهو المرحلة التي تأتي بعد المصادقة، وفيها يحدد النظام ما الذي يُسمح للمستخدم بالوصول إليه أو تنفيذه. لذلك، يمكن لمستخدمين اثنين تسجيل الدخول بنجاح، لكن تختلف صلاحيات كل منهما بحسب الدور أو السياسة الأمنية المعتمدة.
ما هو CORS ولماذا نحتاج إليه؟
يشير CORS إلى Cross-Origin Resource Sharing، وهو آلية تعتمد على ترويسات HTTP تسمح للخادم بتحديد المصادر الخارجية المصرّح لها بالوصول إلى موارده. تظهر أهمية هذه الآلية عندما يكون لديك واجهة أمامية تعمل على نطاق أو منفذ مختلف عن الخادم الخلفي.
كما يعتمد المتصفح أحياناً على طلب تمهيدي يسمى preflight request للتأكد من أن الخادم يسمح فعلاً بالطلب القادم من مصدر مختلف قبل تنفيذ الطلب الفعلي.
ما هي رموز JWT؟
رموز JSON Web Token أو JWT هي معيار مفتوح يُستخدم لنقل المعلومات بين طرفين بشكل آمن على هيئة كائن JSON موقّع. هذا التوقيع يجعل من الممكن التحقق من صحة البيانات والثقة بمصدرها، سواء باستخدام مفتاح سري أو زوج مفاتيح عام وخاص.
في سياق توثيق المستخدمين، يُنشأ رمز JWT بعد نجاح التسجيل أو تسجيل الدخول، ثم يُرسل مع الطلبات اللاحقة للوصول إلى المسارات المحمية.
إعداد مشروع Node.js من البداية
ابدأ بإنشاء مجلد للمشروع وتهيئته باستخدام npm:
mkdir cors-auth-project
cd cors-auth-project
npm init -y
إذا كنت تستخدم محرر Visual Studio Code، يمكنك فتح المشروع بالأمر التالي:
code .
تأكد فقط من أن أمر code مفعّل على جهازك قبل تنفيذه.
إنشاء الملفات والمجلدات الأساسية
بعد تهيئة المشروع، أنشئ البنية التالية:
mkdir model middleware config
touch config/database.js middleware/auth.js model/user.js
touch app.js index.js
ستحصل بذلك على هيكل منظم يسهل صيانته وتطويره لاحقاً.

تثبيت الحزم المطلوبة
سنحتاج إلى مجموعة من الحزم الأساسية لبناء التطبيق:
expressلإنشاء الخادم والمسارات.mongooseللاتصال بقاعدةMongoDB.jsonwebtokenلإنشاء رموزJWTوالتحقق منها.dotenvلقراءة متغيرات البيئة.bcryptjsلتشفير كلمات المرور.corsلتفعيل مشاركة الموارد بين المصادر المختلفة.nodemonلتحديث الخادم تلقائياً أثناء التطوير.
npm install cors mongoose express jsonwebtoken dotenv bcryptjs
npm install nodemon -D
إنشاء الخادم وربط قاعدة البيانات
ملف config/database.js
const mongoose = require("mongoose");
const { MONGO_URI } = process.env;
exports.connect = () => {
mongoose
.connect(MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
})
.then(() => {
console.log("Successfully connected to database");
})
.catch((error) => {
console.log("database connection failed. exiting now...");
console.error(error);
process.exit(1);
});
};
ملف app.js
require("dotenv").config();
require("./config/database").connect();
const express = require("express");
const app = express();
app.use(express.json());
// Logic goes here
module.exports = app;
ملف index.js
const http = require("http");
const app = require("./app");
const server = http.createServer(app);
const { API_PORT } = process.env;
const port = process.env.PORT || API_PORT;
server.listen(port, () => {
console.log(`Server running on port ${port}`);
});
ملف .env
API_PORT=4001
MONGO_URI=your_database_uri
TOKEN_KEY=your_random_secret_key
تحديث سكربتات package.json
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
}
بعد هذه الخطوات، يمكنك تشغيل المشروع بالأمر npm run dev. إذا كانت الإعدادات سليمة، فسيعمل الخادم ويتصل بقاعدة البيانات بنجاح.
إنشاء نموذج المستخدم User Model
الخطوة التالية هي تعريف بنية المستخدم داخل قاعدة البيانات عبر Mongoose:
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
first_name: { type: String, default: null },
last_name: { type: String, default: null },
email: { type: String, unique: true },
password: { type: String },
token: { type: String },
});
module.exports = mongoose.model("user", userSchema);
هذا النموذج يحفظ الاسم الأول، والاسم الأخير، والبريد الإلكتروني، وكلمة المرور المشفّرة، بالإضافة إلى الرمز الخاص بالمستخدم عند الحاجة.
إنشاء مساري التسجيل وتسجيل الدخول
في ملف app.js، أضف الاستيراد التالي ثم أنشئ المسارات الأساسية:
const User = require("./model/user");
app.post("/register", (req, res) => {
// our register logic goes here...
});
app.post("/login", (req, res) => {
// our login logic goes here...
});
تنفيذ منطق التسجيل /register
عند تسجيل مستخدم جديد، نحتاج إلى تنفيذ سلسلة من الخطوات الأمنية:
- قراءة البيانات القادمة من الطلب.
- التحقق من اكتمال الحقول المطلوبة.
- التأكد من أن المستخدم غير موجود مسبقاً.
- تشفير كلمة المرور قبل تخزينها.
- إنشاء المستخدم داخل قاعدة البيانات.
- توليد رمز
JWTوإرجاعه مع بيانات المستخدم.
app.post("/register", async (req, res) => {
try {
const { firstName, lastName, email, password } = req.body;
if (!(email && password && firstName && lastName)) {
return res.status(400).send("All input is required");
}
const oldUser = await User.findOne({ email });
if (oldUser) {
return res.status(409).send("User Already Exist. Please Login");
}
const encryptedUserPassword = await bcrypt.hash(password, 10);
const user = await User.create({
first_name: firstName,
last_name: lastName,
email: email.toLowerCase(),
password: encryptedUserPassword,
});
const token = jwt.sign(
{ user_id: user._id, email },
process.env.TOKEN_KEY,
{ expiresIn: "5h" }
);
user.token = token;
return res.status(201).json(user);
} catch (err) {
console.log(err);
}
});
بعد نجاح التسجيل، يمكنك اختبار المسار باستخدام Postman للتأكد من إرجاع بيانات المستخدم مع الرمز الأمني.

تنفيذ منطق تسجيل الدخول /login
في هذه المرحلة، نتحقق من البريد الإلكتروني وكلمة المرور، ثم نقارن كلمة المرور المدخلة بالنسخة المشفّرة داخل قاعدة البيانات:
app.post("/login", async (req, res) => {
try {
const { email, password } = req.body;
if (!(email && password)) {
return res.status(400).send("All input is required");
}
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
const token = jwt.sign(
{ user_id: user._id, email },
process.env.TOKEN_KEY,
{ expiresIn: "5h" }
);
user.token = token;
return res.status(200).json(user);
}
return res.status(400).send("Invalid Credentials");
} catch (err) {
console.log(err);
}
});
هذه الخطوة أساسية لأنها تفصل بين التحقق من الهوية وتوليد الرمز الخاص بالجلسة أو الطلبات اللاحقة.

إنشاء Middleware لحماية المسارات
بعد أن أصبح بالإمكان تسجيل المستخدمين وتسجيل دخولهم، نحتاج إلى حماية بعض المسارات بحيث لا يمكن الوصول إليها إلا بوجود رمز صالح من نوع JWT.
ملف middleware/auth.js
const jwt = require("jsonwebtoken");
const config = process.env;
const verifyToken = (req, res, next) => {
const token =
req.body.token || req.query.token || req.headers["x-access-token"];
if (!token) {
return res.status(403).send("A token is required for authentication");
}
try {
const decoded = jwt.verify(token, config.TOKEN_KEY);
req.user = decoded;
} catch (err) {
return res.status(401).send("Invalid Token");
}
return next();
};
module.exports = verifyToken;
وظيفة هذا الوسيط هي استخراج الرمز من الطلب، والتحقق من صحته، ثم تمرير التنفيذ إلى المسار التالي إذا كان كل شيء سليماً.
إنشاء مسار محمي للتجربة
لاختبار الوسيط الأمني، أضف هذا المسار داخل app.js:
const auth = require("./middleware/auth");
app.post("/welcome", auth, (req, res) => {
res.status(200).send("Welcome to FreeCodeCamp 🙌");
});
إذا حاولت الوصول إلى المسار /welcome دون إرسال ترويسة x-access-token، فستحصل على رسالة رفض مناسبة.

أما إذا أرسلت رمزاً صحيحاً داخل الترويسة x-access-token، فسيتم السماح بالوصول إلى المسار المحمي بنجاح.

كيفية تفعيل CORS في تطبيق Node.js
توفر حزمة cors وسيطاً جاهزاً يسهل دمجه مع Express للتحكم في الطلبات القادمة من مصادر مختلفة.
تفعيل CORS لكل الطلبات
إذا أردت السماح بجميع الطلبات من مختلف المصادر أثناء التطوير أو في بيئات محددة، أضف ما يلي إلى app.js:
const cors = require("cors");
const app = express();
app.use(cors());
app.use(express.json({ limit: "50mb" }));
هذا الإعداد بسيط، لكنه لا يكون دائماً الخيار الأمثل في البيئات الإنتاجية الحساسة.
تفعيل CORS لمسار واحد فقط
إذا كنت تريد مزيداً من التحكم، يمكنك تفعيل CORS لمسار محدد فقط:
app.get("/welcome", cors(), auth, (req, res) => {
res.status(200).send("Welcome to FreeCodeCamp 🙌 ");
});
هذا الأسلوب مفيد عندما تحتاج إلى تقييد الوصول الخارجي على نقاط معينة دون غيرها.
تهيئة خيارات CORS بشكل مخصص
يمكنك تخصيص المصدر المسموح به وبعض الخيارات الأخرى كما يلي:
const corsOptions = {
origin: "http://example.com",
optionsSuccessStatus: 200,
};
app.get("/welcome", cors(corsOptions), auth, (req, res) => {
res.status(200).send("Welcome to FreeCodeCamp 🙌 ");
});
يفيد هذا الأسلوب عند ربط الواجهة الأمامية مع الخادم الخلفي في بيئة إنتاج، حيث يجب تحديد النطاقات الموثوقة بدقة بدلاً من فتح الوصول للجميع.
أفضل ممارسات مهمة لتحسين الأمان والجودة
- لا تخزن كلمة المرور بصيغتها الخام إطلاقاً، واستخدم دائماً
bcryptjsأو بديله المناسب. - احتفظ بالمفاتيح السرية مثل
TOKEN_KEYداخل ملف.envوليس داخل الشيفرة. - اجعل مدة صلاحية رمز
JWTمناسبة لطبيعة التطبيق. - لا تستخدم إعداد
CORSالمفتوح في الإنتاج إلا لضرورة واضحة ومدروسة. - أضف تحققاً إضافياً للمدخلات باستخدام مكتبات متخصصة إذا كان المشروع حقيقياً أو عاماً.
الخلاصة التقنية
الجمع بين JWT ووسيط حماية مخصص وتهيئة سليمة لـ CORS يمنح تطبيقات Node.js أساساً أمنياً جيداً وقابلاً للتوسع. عملياً، أفضل نهج هو الفصل بين المصادقة والتفويض، وتقييد المصادر المسموح لها بالوصول، والتعامل مع الرموز السرية ومتغيرات البيئة بحذر. بهذه الطريقة، لا تكتفي ببناء API يعمل فقط، بل تبني واجهة أكثر أماناً واستقراراً وملاءمة للاستخدام الفعلي.