طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 302 · من الفكرة إلى المنتج (٢): نظام حجز مواعيد
الدرس 5 · الوحدة 3: التأمين والواجهة التفاعلية

المحطّة ٥: المصادقة بالرموز والأدوار (مريض/موظّف)

طبقة الأمان: رمزٌ يعرّف المستخدم، ودورٌ يحكم ما يحقّ له. ⏱ نحو 34 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تفرّق بين المصادقة والتفويض.
  • تصدر رموز JWT تحمل الدور وتتحقّق منها في وسيط.
  • تحمي المسارات باشتراط الدور المناسب.
  • تختار بين 401 و403 بدقّة.

١مصادقةٌ وتفويض: سؤالان مختلفان

في نظامٍ بدورين نفرّق — كما في مقرّر 243 — بين سؤالين:

نستخدم 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);

توقّع وتحقّق

«مَن أنت؟» سؤال:

أين يُحفظ دور المستخدم بأمان؟

مريضٌ مُصادَقٌ يطلب مسار الموظّف ← الرمز:

مُصادَقٌ لكن ممنوع.

طلبٌ بلا رمزٍ على مسارٍ محميّ ←

الخلاصة

تحدٍّ برمجي: الدخول والأدوار

برمجي

أضِف طبقة الأمان: دخولٌ يصدر رمزًا يحمل الدور، ووسيطا تحقّقٍ وتفويض، ومسار جدولٍ للموظّف فقط. المريض المُصادَق يُمنَع منه.

المطلوب:
  • POST /api/login: يتحقّق ويعيد {"token": …} يحمل role، أو 401 عند الفشل.
  • وسيط auth يتحقّق من الرمز (401 إن غاب/فسد)، وrequireRole("staff") يمنع غير الموظّف بـ 403.
  • GET /api/schedule محميٌّ للموظّف فقط ويرد كل المواعيد.
👀 اعرض الاستجابات المطلوبة
✓ التحقق الآلي: يشغّل المختبر خادمك من جديد ويرسل إليه هذه الطلبات بالترتيب ثم يفحص الردود
  1. POST /api/login {"username": "منى", "password": "admin"}
  2. POST /api/login {"username": "سعود", "password": "1234"}
  3. POST /api/login {"username": "سعود", "password": "wrong"}
  4. GET /api/schedule 🔑
  5. GET /api/schedule 🔑
  6. GET /api/schedule
  • الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
  • الدخول يعيد رمزًا نصيًّا
  • الدخول الصحيح ← 200
  • الدخول الخاطئ ← 401
  • الموظّف يرى الجدول ← 200
  • … وفيه كل المواعيد
  • المريض المُصادَق يُمنَع ← 403
  • … برمز FORBIDDEN
  • بلا رمز ← 401

💡 تلميح 1
الدخول: جِد المستخدم، ثم jwt.sign({ sub, role }, SECRET).
💡 تلميح 2
auth: اقرأ Authorization، احذف "Bearer "، ثم jwt.verify داخل try/catch.
💡 تلميح 3
requireRole: دالّةٌ تعيد وسيطًا يقارن 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);

🐞 لاحظت خطأً في هذا الدرس؟ أبلغني بالبريد

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس