المحطّة ٥: المصادقة بالرموز والأدوار (مريض/موظّف)
طبقة الأمان: رمزٌ يعرّف المستخدم، ودورٌ يحكم ما يحقّ له. ⏱ نحو 34 دقيقة.
- تفرّق بين المصادقة والتفويض.
- تصدر رموز
JWTتحمل الدور وتتحقّق منها في وسيط. - تحمي المسارات باشتراط الدور المناسب.
- تختار بين 401 و403 بدقّة.
١مصادقةٌ وتفويض: سؤالان مختلفان
في نظامٍ بدورين نفرّق — كما في مقرّر 243 — بين سؤالين:
- المصادقة
(Authentication): مَن أنت؟ نثبتها بالدخول، ونعطي المستخدم رمزًا(Token)يحمله في كل طلب. - التفويض
(Authorization): ماذا يحقّ لك؟ نقرأ الدور من الرمز، فنسمح للموظّف برؤية الجدول كاملًا ونمنع المريض.
نستخدم JWT: عند الدخول نوقّع رمزًا يحوي هويّة المستخدم ودوره، ويرسله المتصفّح في ترويسة Authorization: Bearer <token> مع كل طلب. الخادم يتحقّق من توقيعه فيثق بمحتواه.
٢الدخول يصدر رمزًا
نتحقّق من البيانات، ثم نوقّع رمزًا يحمل اسم المستخدم ودوره:
app.post('/api/login', (req, res) => { const u = users.find(x => x.username === req.body.username && x.password === req.body.password); if (!u) return res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'بيانات غير صحيحة' } }); const token = jwt.sign({ sub: u.username, role: u.role }, SECRET, { expiresIn: '1h' }); res.json({ token });});
الدور role داخل الرمز الموقّع هو مفتاح التفويض: لا يستطيع المريض تزوير دوره لأن أيّ تعديلٍ يُفسد التوقيع.
٣وسيطان: التحقّق واشتراط الدور
نكتب وسيطين (Middleware) نركّبهما على المسارات المحميّة: الأوّل يتحقّق من الرمز، والثاني يشترط دورًا:
// وسيطٌ يتحقّق من الرمز ويضع صاحبه في req.userfunction auth(req, res, next) { const token = (req.headers.authorization || '').replace('Bearer ', ''); try { req.user = jwt.verify(token, SECRET); next(); } catch { res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'سجّل الدخول' } }); }}// وسيطٌ يشترط دورًا معيّنًاconst requireRole = (role) => (req, res, next) => req.user.role === role ? next() : res.status(403).json({ error: { code: 'FORBIDDEN', message: 'غير مصرّح' } });
نجرّب: ندخل موظّفًا ومريضًا، ثم نطلب الجدول (للموظّف فقط) بكلٍّ منهما، ثم بلا رمز:
const express = require('express');const jwt = require('jsonwebtoken');const app = express();app.use(express.json());const SECRET = process.env.JWT_SECRET || 'clinic-secret-very-long-2026'; // مستخدمو تجربة (في الإنتاج تُعمّى كلمات المرور كما في مقرّري 243 و301)const users = [ { username: 'منى', password: 'admin', role: 'staff' }, { username: 'سعود', password: '1234', role: 'patient' },]; let appts = [ { id: 1, doctor: 'د. منى', time: '09:00', bookedBy: null }, { id: 2, doctor: 'د. منى', time: '09:30', bookedBy: 'سارة' }, { id: 3, doctor: 'د. أحمد', time: '10:00', bookedBy: null },]; app.post('/api/login', (req, res) => { const u = users.find(x => x.username === req.body.username && x.password === req.body.password); if (!u) return res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'بيانات غير صحيحة' } }); const token = jwt.sign({ sub: u.username, role: u.role }, SECRET, { expiresIn: '1h' }); res.json({ token });}); // وسيطٌ يتحقّق من الرمز ويضع صاحبه في req.userfunction auth(req, res, next) { const token = (req.headers.authorization || '').replace('Bearer ', ''); try { req.user = jwt.verify(token, SECRET); next(); } catch { res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'سجّل الدخول' } }); }}// وسيطٌ يشترط دورًا معيّنًاconst requireRole = (role) => (req, res, next) => req.user.role === role ? next() : res.status(403).json({ error: { code: 'FORBIDDEN', message: 'غير مصرّح' } }); app.get('/api/schedule', auth, requireRole('staff'), (req, res) => { res.json({ data: appts, meta: { total: appts.length } });}); app.listen(3000);
توقّع وتحقّق
«مَن أنت؟» سؤال:
أين يُحفظ دور المستخدم بأمان؟
مريضٌ مُصادَقٌ يطلب مسار الموظّف ← الرمز:
طلبٌ بلا رمزٍ على مسارٍ محميّ ←
الخلاصة
- المصادقة «مَن أنت» بالدخول والرمز؛ التفويض «ماذا يحقّ لك» بالدور.
JWTيحمل الهويّة والدور موقّعين؛ أيّ تعديلٍ يُفسد التوقيع.- وسيط
authيتحقّق ويضعreq.user؛requireRoleيشترط الدور. - 401 = لم تُصادَق، 403 = مُصادَقٌ لكن ممنوع.
- التفويض في الخادم لا الواجهة؛ والسرّ خارج الكود المُشارَك.
تحدٍّ برمجي: الدخول والأدوار
برمجيأضِف طبقة الأمان: دخولٌ يصدر رمزًا يحمل الدور، ووسيطا تحقّقٍ وتفويض، ومسار جدولٍ للموظّف فقط. المريض المُصادَق يُمنَع منه.
POST /api/login: يتحقّق ويعيد{"token": …}يحملrole، أو 401 عند الفشل.- وسيط
authيتحقّق من الرمز (401 إن غاب/فسد)، وrequireRole("staff")يمنع غير الموظّف بـ 403. GET /api/scheduleمحميٌّ للموظّف فقط ويرد كل المواعيد.
👀 اعرض الاستجابات المطلوبة
POST /api/login{"username": "منى", "password": "admin"}POST /api/login{"username": "سعود", "password": "1234"}POST /api/login{"username": "سعود", "password": "wrong"}GET /api/schedule🔑GET /api/schedule🔑GET /api/schedule
- الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
- الدخول يعيد رمزًا نصيًّا
- الدخول الصحيح ← 200
- الدخول الخاطئ ← 401
- الموظّف يرى الجدول ← 200
- … وفيه كل المواعيد
- المريض المُصادَق يُمنَع ← 403
- … برمز FORBIDDEN
- بلا رمز ← 401
💡 تلميح 1
jwt.sign({ sub, role }, SECRET).💡 تلميح 2
Authorization، احذف "Bearer "، ثم jwt.verify داخل try/catch.💡 تلميح 3
req.user.role بالمطلوب.حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
const express = require('express');const jwt = require('jsonwebtoken');const app = express();app.use(express.json());const SECRET = process.env.JWT_SECRET || 'clinic-secret-very-long-2026'; // مستخدمو تجربة (في الإنتاج تُعمّى كلمات المرور كما في مقرّري 243 و301)const users = [ { username: 'منى', password: 'admin', role: 'staff' }, { username: 'سعود', password: '1234', role: 'patient' },]; let appts = [ { id: 1, doctor: 'د. منى', time: '09:00', bookedBy: null }, { id: 2, doctor: 'د. منى', time: '09:30', bookedBy: 'سارة' }, { id: 3, doctor: 'د. أحمد', time: '10:00', bookedBy: null },]; app.post('/api/login', (req, res) => { const u = users.find(x => x.username === req.body.username && x.password === req.body.password); if (!u) return res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'بيانات غير صحيحة' } }); const token = jwt.sign({ sub: u.username, role: u.role }, SECRET, { expiresIn: '1h' }); res.json({ token });}); // وسيطٌ يتحقّق من الرمز ويضع صاحبه في req.userfunction auth(req, res, next) { const token = (req.headers.authorization || '').replace('Bearer ', ''); try { req.user = jwt.verify(token, SECRET); next(); } catch { res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'سجّل الدخول' } }); }}// وسيطٌ يشترط دورًا معيّنًاconst requireRole = (role) => (req, res, next) => req.user.role === role ? next() : res.status(403).json({ error: { code: 'FORBIDDEN', message: 'غير مصرّح' } }); app.get('/api/schedule', auth, requireRole('staff'), (req, res) => { res.json({ data: appts, meta: { total: appts.length } });}); app.listen(3000);