Node.js: المصادقة والأمان

آخر تحديث: 2026-08-26

1. القصة: واجهة برمجة تطبيقات غير محمية

أمضت أليس أسبوعين في نشر واجهة برمجة التطبيقات (API) الخاصة بإدارة المستخدمين، لتكتشف في اليوم التالي أن قاعدة البيانات قد حُذفت — حيث لم تكن واجهة برمجة التطبيقات الخاصة بها مزودة بنظام مصادقة، مما سمح لأي شخص باستدعائها مباشرةً DELETE /api/users. وبعد أن تعلمت الدرس، قامت بتنفيذ مصادقة JWT: حيث يتعين على المستخدمين أولاً تسجيل الدخول للحصول على رمز مميز (token)، ويجب أن تتضمن الطلبات اللاحقة هذا الرمز للوصول إلى المسارات المحمية. وفي الوقت نفسه، أضافت برمجيات وسيطة قائمة على الأدوار لعمليات الإدارة، مما يضمن عدم تمكن حتى المستخدمين الذين يمتلكون رمزًا من تنفيذ إجراءات خطيرة. وبعد أسبوع، استخدمت Helmet لإضافة إجراءات أمنية إلى رؤوس الاستجابة، وأصبحت واجهة برمجة التطبيقات (API) أخيرًا آمنة كالحصن.



2. المفاهيم الأساسية

(1) الفرق بين المصادقة والتفويض

(2) نظرة عامة على مبادئ JWT

(3) تجزئة كلمات المرور باستخدام bcrypt



3. التفاصيل الفنية

(1) هيكل ومبادئ عمل JWT

يتكون JWT من ثلاثة أجزاء مرتبطة ببعضها بواسطة .:

TEXT 📖 للعرض فقط
Header.Payload.Signature
القسم المحتوى الوصف
العنوان { "alg": "HS256", "typ": "JWT" } الخوارزميات وهياكل البيانات
الحمولة { "userId": 1, "role": "admin", "exp": ... } إقرار مخصص + إقرار قياسي
التوقيع HMACSHA256(base64(header) + "." + base64(payload), secret) توقيع مقاوم للتلاعب

مسار العمل:

  1. يقوم المستخدم بتسجيل الدخول؛ ويقوم الخادم بالتحقق من بيانات الاعتماد
  2. إذا نجحت عملية المصادقة، فقم بإصدار JWT وإرساله إلى العميل
  3. يقوم العميل بتخزين الرمز المميز في localStorage أو في ملف تعريف ارتباط
  4. تتضمن الطلبات اللاحقة العناصر التالية في رأس Authorization: Bearer <token>:
  5. تقوم البرمجيات الوسيطة من جانب الخادم بالتحقق من التوقيع وتاريخ انتهاء الصلاحية

▶ مثال: إصدار JWT والتحقق منه

JAVASCRIPT
const jwt = require('jsonwebtoken');

const SECRET = 'my_super_secret_key';

const token = jwt.sign(
  { userId: 42, role: 'admin' },
  SECRET,
  { expiresIn: '2h' }
);

console.log('Token:', token);

const decoded = jwt.verify(token, SECRET);
console.log('Decoded:', decoded);
▶ جرّب الكود

▶ مثال: فك تشفير JWT لعرض الحمولة (دون التحقق من التوقيع)

JAVASCRIPT
const decoded = jwt.decode(token, { complete: true });
console.log('Header:', decoded.header);
console.log('Payload:', decoded.payload);
▶ جرّب الكود

(2) معلمات bcrypt وكيفية استخدامها

المعلمة القيمة الموصى بها الوصف
saltRounds 10-12 عدد الجولات؛ كلما زاد العدد، زادت الأمان ولكن زادت البطء
طول كلمة المرور ≥8 أحرف يتم التحقق من صحتها من خلال واجهة المستخدم
الخوارزميات Blowfish الخوارزميات الأساسية لـ bcrypt

▶ مثال: تجزئة كلمة المرور والتحقق منها

JAVASCRIPT
const bcrypt = require('bcrypt');

async function hashPassword(plainPassword) {
  const saltRounds = 10;
  const hash = await bcrypt.hash(plainPassword, saltRounds);
  console.log('Hash:', hash);
  return hash;
}

async function verifyPassword(plainPassword, hash) {
  const match = await bcrypt.compare(plainPassword, hash);
  console.log('Match:', match);
  return match;
}

(async () => {
  const hash = await hashPassword('MyPassword123');
  await verifyPassword('MyPassword123', hash);
  await verifyPassword('WrongPassword', hash);
})();
▶ جرّب الكود

(3) رؤوس أمان HTTP و Helmet

يعزز Helmet الأمان من خلال تعيين رؤوس استجابة HTTP:

عنوان الأمان الغرض ممكّن افتراضيًا
سياسة أمان المحتوى تمنع هجمات XSS وتقيد المصادر التي يمكن تحميل الموارد منها لا (تتطلب تهيئة يدوية)
X-Frame-Options منع اختطاف النقرات نعم
X-Content-Type-Options منع استكشاف نوع MIME نعم
الأمان الصارم للنقل فرض استخدام HTTPS نعم
X-XSS-Protection تصفية XSS في المتصفح مهملة (لم يعد Helmet يقوم بتمكين هذه الميزة افتراضيًا)

▶ مثال: دمج Helmet

JAVASCRIPT
const express = require('express');
const helmet = require('helmet');

const app = express();

app.use(helmet());

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],
    styleSrc: ["'self'", "'unsafe-inline'"],
  }
}));

app.get('/', (req, res) => {
  res.send('Secured with helmet!');
});

app.listen(3000);
▶ جرّب الكود

(4) التهديدات الأمنية الشائعة والتدابير المضادة

التهديد الآلية الإجراءات المضادة
حقن SQL ربط مدخلات المستخدم لإنشاء جملة SQL ضارة الاستعلامات المعلمة / ORM
XSS (التنفيذ البرمجي عبر المواقع) إدخال نصوص برمجية ضارة في الصفحات التهرب من الإخراج / CSP / Helmet
CSRF (تزوير الطلبات عبر المواقع) إرسال الطلبات مع انتحال صفة مستخدم تمت مصادقته رمز CSRF / ملف تعريف الارتباط SameSite
هجمات القوة الغاشمة محاولات تسجيل الدخول المتكررة تقييد معدل المحاولات / عدد جولات bcrypt المرتفع / حظر الحساب
هجمات «الرجل في الوسط» اعتراض بيانات الاتصالات HTTPS / HSTS

▶ مثال: منع هجمات حقن SQL (استعلامات معلمة)

JAVASCRIPT
const { Pool } = require('pg');
const pool = new Pool();

app.post('/login', async (req, res) => {
  const { username, password } = req.body;

  const result = await pool.query(
    'SELECT * FROM users WHERE username = $1',
    [username]
  );

  if (result.rows.length === 0) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const user = result.rows[0];
  const match = await bcrypt.compare(password, user.password_hash);
  if (!match) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const token = jwt.sign({ userId: user.id, role: user.role }, SECRET, { expiresIn: '2h' });
  res.json({ token });
});
▶ جرّب الكود

▶ مثال: منع هجمات XSS (تشفير المخرجات)

JAVASCRIPT
function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
}

const userInput = '<script>alert("xss")</script>';
console.log(escapeHtml(userInput));
▶ جرّب الكود

▶ مثال: الحماية من CSRF (برنامج الوسيط csurf)

JAVASCRIPT
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.get('/form', csrfProtection, (req, res) => {
  res.json({ csrfToken: req.csrfToken() });
});

app.post('/submit', csrfProtection, (req, res) => {
  res.json({ message: 'Form submitted successfully' });
});
▶ جرّب الكود

4. تمارين عملية

(1) واجهة برمجة التطبيقات (API) للتسجيل

▶ مثال: واجهة برمجة تطبيقات (API) لتسجيل المستخدمين

JAVASCRIPT
app.post('/api/register', async (req, res) => {
  const { username, password, role } = req.body;

  if (!username || !password) {
    return res.status(400).json({ error: 'Username and password are required' });
  }

  if (password.length < 8) {
    return res.status(400).json({ error: 'Password must be at least 8 characters' });
  }

  const existingUser = await pool.query(
    'SELECT id FROM users WHERE username = $1',
    [username]
  );
  if (existingUser.rows.length > 0) {
    return res.status(409).json({ error: 'Username already exists' });
  }

  const saltRounds = 10;
  const passwordHash = await bcrypt.hash(password, saltRounds);

  const result = await pool.query(
    'INSERT INTO users (username, password_hash, role) VALUES ($1, $2, $3) RETURNING id, username, role',
    [username, passwordHash, role || 'user']
  );

  const user = result.rows[0];
  const token = jwt.sign(
    { userId: user.id, role: user.role },
    SECRET,
    { expiresIn: '2h' }
  );

  res.status(201).json({ user: { id: user.id, username: user.username, role: user.role }, token });
});
▶ جرّب الكود

(2) واجهة برمجة تطبيقات تسجيل الدخول

▶ مثال: واجهة برمجة تطبيقات تسجيل دخول المستخدم

JAVASCRIPT
app.post('/api/login', async (req, res) => {
  const { username, password } = req.body;

  if (!username || !password) {
    return res.status(400).json({ error: 'Username and password are required' });
  }

  const result = await pool.query(
    'SELECT id, username, password_hash, role FROM users WHERE username = $1',
    [username]
  );

  if (result.rows.length === 0) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const user = result.rows[0];
  const match = await bcrypt.compare(password, user.password_hash);

  if (!match) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const token = jwt.sign(
    { userId: user.id, username: user.username, role: user.role },
    SECRET,
    { expiresIn: '2h' }
  );

  res.json({
    user: { id: user.id, username: user.username, role: user.role },
    token
  });
});
▶ جرّب الكود

(3) البرمجيات الوسيطة لمصادقة JWT

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

JAVASCRIPT
function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Access denied. No token provided.' });
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired' });
    }
    return res.status(403).json({ error: 'Invalid token' });
  }
}
▶ جرّب الكود

(4) البرمجيات الوسيطة للتحكم في الوصول القائم على الأدوار

▶ مثال: برنامج وسيط لدور "authorize"

JAVASCRIPT
function authorize(...roles) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }

    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }

    next();
  };
}

app.get('/api/profile', authenticate, (req, res) => {
  res.json({ user: req.user });
});

app.delete('/api/users/:id', authenticate, authorize('admin'), async (req, res) => {
  await pool.query('DELETE FROM users WHERE id = $1', [req.params.id]);
  res.json({ message: 'User deleted' });
});

app.get('/api/admin/dashboard', authenticate, authorize('admin'), (req, res) => {
  res.json({ message: 'Welcome to admin dashboard' });
});
▶ جرّب الكود

5. مثال شامل: نظام مصادقة كامل

TEXT 📖 للعرض فقط
project/
├── server.js
├── middleware/
│   ├── auth.js
│   └── role.js
├── routes/
│   ├── auth.js
│   └── users.js
└── パッケージ.json

middleware/auth.js:

JAVASCRIPT
const jwt = require('jsonwebtoken');

const SECRET = process.env.JWT_SECRET || 'fallback_dev_secret';

function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Access denied. No token provided.' });
  }

  const token = authHeader.split(' ')[1];
  try {
    const decoded = jwt.verify(token, SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired. Please login again.' });
    }
    return res.status(403).json({ error: 'Invalid token.' });
  }
}

module.exports = { authenticate, SECRET };

البرمجيات الوسيطة/role.js:

JAVASCRIPT
function authorize(...roles) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required.' });
    }
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden. Insufficient permissions.' });
    }
    next();
  };
}

module.exports = { authorize };

routes/auth.js:

JAVASCRIPT
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { authenticate, SECRET } = require('../middleware/auth');

const router = express.Router();

const users = [];

router.post('/register', غير متزامن (req, res) => {
  const { username, password, role } = req.body;
  if (!username || !password) {
    return res.status(400).json({ error: 'Username and password required.' });
  }
  if (password.length < 8) {
    return res.status(400).json({ error: 'Password must be at least 8 characters.' });
  }
  const exists = users.find(u => u.username === username);
  if (exists) {
    return res.status(409).json({ error: 'Username already exists.' });
  }

  const saltRounds = 10;
  const passwordHash = انتظار bcrypt.hash(password, saltRounds);
  const newUser = {
    id: users.length + 1,
    username,
    passwordHash,
    role: role || 'user'
  };
  users.push(newUser);

  const token = jwt.sign(
    { userId: newUser.id, username: newUser.username, role: newUser.role },
    SECRET,
    { expiresIn: '2h' }
  );

  res.status(201).json({
    user: { id: newUser.id, username: newUser.username, role: newUser.role },
    token
  });
});

router.post('/login', غير متزامن (req, res) => {
  const { username, password } = req.body;
  if (!username || !password) {
    return res.status(400).json({ error: 'Username and password required.' });
  }

  const user = users.find(u => u.username === username);
  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials.' });
  }

  const match = انتظار bcrypt.compare(password, user.passwordHash);
  if (!match) {
    return res.status(401).json({ error: 'Invalid credentials.' });
  }

  const token = jwt.sign(
    { userId: user.id, username: user.username, role: user.role },
    SECRET,
    { expiresIn: '2h' }
  );

  res.json({
    user: { id: user.id, username: user.username, role: user.role },
    token
  });
});

router.get('/profile', authenticate, (req, res) => {
  const user = users.find(u => u.id === req.user.userId);
  if (!user) {
    return res.status(404).json({ error: 'User not found.' });
  }
  res.json({ id: user.id, username: user.username, role: user.role });
});

module.exports = router;

routes/users.js:

JAVASCRIPT
const express = require('express');
const { authenticate } = require('../middleware/auth');
const { authorize } = require('../middleware/role');

const router = express.Router();

const users = [];

router.get('/', authenticate, authorize('admin'), (req, res) => {
  const safeList = users.map(u => ({ id: u.id, username: u.username, role: u.role }));
  res.json(safeList);
});

router.delete('/:id', authenticate, authorize('admin'), (req, res) => {
  const index = users.findIndex(u => u.id === parseInt(req.params.id));
  if (index === -1) {
    return res.status(404).json({ error: 'User not found.' });
  }
  users.splice(index, 1);
  res.json({ message: 'User deleted.' });
});

module.exports = router;

server.js:

JAVASCRIPT
const express = require('express');
const helmet = require('helmet');
const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/users');

const app = express();

app.use(helmet());
app.use(express.json());

app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes);

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Internal server error.' });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

عملية الاختبار:

BASH
# Register
curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"SecurePass123","role":"admin"}'

# Log In
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"SecurePass123"}'

# Accessing Protected Routes
curl http://localhost:3000/api/auth/profile \
  -H "Authorization: Bearer <your_token>"

# Administrator Actions
curl -X DELETE http://localhost:3000/api/users/2 \
  -H "Authorization: Bearer <admin_token>"
TEXT 📖 للعرض فقط
# Registration Response
{
  "user": { "id": 1, "username": "alice", "role": "admin" },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

# Login Response
{
  "user": { "id": 1, "username": "alice", "role": "admin" },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

# Insufficient Permissions Response
{ "error": "Forbidden. Insufficient permissions." }


6. مخطط تدفق عملية المصادقة باستخدام JWT

100%
sequenceDiagram
    participant Client as Client
    participant Server as Server-side
    participant DB as Database

    Client->>Server: POST /api/login {username, password}
    Server->>DB: Query User Records
    DB-->>Server: Return User Data
    Server->>Server: bcrypt.compare() Confirm Password
    alt The password is correct
        Server->>Server: jwt.sign() Issued Token
        Server-->>Client: Back { token }
        Client->>Client: Storage Token
        Client->>Server: GET /api/profile<br/>Authorization: Bearer <token>
        Server->>Server: jwt.verify() Verification Token
        alt Token Valid
            Server-->>Client: 200 Return User Data
        else Token Invalid or Expired
            Server-->>Client: 401/403 Access Denied
        end
    else Incorrect password
        Server-->>Client: 401 Invalid credentials
    end


7. المقارنات والمراجع

(1) JWT مقابل الجلسة مقابل OAuth

البعد JWT الجلسة OAuth 2.0
موقع التخزين العميل الخادم الخادم + العميل
بلا جنسية نعم لا لا
قابلية التوسع يدعم الأنظمة الموزعة بشكل أصلي يتطلب تخزينًا مشتركًا للجلسات يتطلب خادم ترخيص
حالات الاستخدام مصادقة واجهة برمجة التطبيقات (API)، الخدمات الصغيرة تطبيقات الويب التقليدية تسجيل الدخول عبر طرف ثالث
المخاطر الأمنية تسرب الرموز التي لا يمكن إلغاؤها اختطاف الجلسة هجمات إعادة التشغيل
التعقيد منخفض منخفض مرتفع

(2) اختيار معلمات bcrypt

جولات salt المدة التقريبية حالات الاستخدام
8 ~40 مللي ثانية التطوير والاختبار
10 ~160 مللي ثانية يُوصى به لبيئات الإنتاج
12 ~640 مللي ثانية متطلبات أمان عالية
14 ~2.5 ثانية سيناريوهات السلامة القصوى

(3) التهديدات الأمنية الشائعة والتدابير المضادة

التهديد مثال على الهجوم طريقة الدفاع الأدوات/المكتبات
حقن SQL ' OR 1=1 -- الاستعلامات المعلمة pg/mysql2
XSS <script>document.cookie</script> Escape + CSP helmet/xss
CSRF تزوير إرسال النماذج رمز CSRF + SameSite csurf
هجوم القوة الغاشمة هجوم القاموس على كلمات المرور تحديد معدل المحاولات + bcrypt ذو عدد جولات مرتفع express-rate-limit
هجمات «الرجل في الوسط» استنشاق حركة مرور HTTP HTTPS + HSTS helmet/Let's Encrypt

(4) الحقول الشائعة في حمولة JWT

الحقل الاسم الكامل الوصف
iss الجهة المُصدرة معرّف الجهة المُصدرة
sub الموضوع الموضوع (عادةً ما يكون معرّف المستخدم)
aud الجمهور معرّف المستلم
exp تاريخ انتهاء الصلاحية وقت انتهاء الصلاحية (طابع زمني بنظام يونكس)
iat مكان الإصدار تاريخ الإصدار
jti معرّف JWT المعرّف الفريد (مضاد لإعادة التشغيل)
userId مخصص معرّف المستخدم التجاري
الدور مخصص دور المستخدم


❓ أسئلة شائعة

س أين يتم تخزين JWT؟
ج يُنصح بتخزينه في ملف تعريف ارتباط httpOnly لمنع هجمات XSS من قراءته؛ أما localStorage فهو وسيلة مريحة لكنه عرضة للسرقة عبر هجمات XSS، لذا يجب استخدامه بالاقتران مع CSP.
س ماذا أفعل إذا انتهت صلاحية الرمز المميز؟
ج تتمثل إحدى الطرق الشائعة التي تعتمد على رمزين مميزين في استخدام رمز وصول قصير الأمد ورمز تحديث طويل الأمد. وعندما تنتهي صلاحية رمز الوصول، استخدم رمز التحديث للحصول على رمز جديد. ويتم تخزين رمز التحديث في قاعدة البيانات بحيث يمكن إلغاؤه بشكل استباقي.
س لماذا يعتبر bcrypt بطيئًا جدًّا؟
ج يتحكم عامل التكلفة في bcrypt في عدد جولات التجزئة؛ فكل جولة إضافية تضاعف وقت المعالجة. وهذه «البطء» مقصود من حيث التصميم، لضمان أن تتكبد هجمات القوة الغاشمة تكلفة حسابية كبيرة مع كل محاولة لتخمين كلمة المرور.
س هل بروتوكول HTTPS إلزامي؟
ج إنه إلزامي في بيئة التشغيل. عند استخدام بروتوكول HTTP، تُرسَل الرموز المميزة وكلمات المرور بنص عادي، مما يجعلها عرضة للاعتراض من قبل أي طرف وسيط؛ لذا فإن الإرسال المشفر عبر بروتوكول HTTPS هو الحد الأدنى من متطلبات الأمان.
س كيف يمكن منع هجمات القوة الغاشمة؟
ج الدفاع متعدد الطبقات — حيث يعمل bcrypt على زيادة التكلفة الحسابية لكل محاولة مع زيادة عدد الجولات، بالإضافة إلى استخدام express-rate-limit لتقييد معدل المحاولات، وآلية قفل الحساب، وتأخير الاستجابة بعد محاولات تسجيل الدخول الفاشلة.
س هل يمكن إلغاء صلاحية JWT بشكل استباقي؟
ج نظرًا لأن JWT عديمة الحالة بطبيعتها، فلا يمكن إلغاء صلاحيتها بشكل استباقي. وتشمل الحلول الشائعة ما يلي: الاحتفاظ بقائمة سوداء (تخزين معرّف JTI للرموز التي تم إلغاء صلاحيتها في Redis حتى انتهاء صلاحيتها)، وتقصير فترة الصلاحية بالاقتران مع رمز التحديث، وطلب مصادقة ثانوية للعمليات الحساسة.
س ما الذي يجب أن آخذه في الاعتبار عند تكوين سياسة CSP لـ Helmet؟
ج إذا كانت قواعد CSP صارمة للغاية، فقد تمنع تحميل الموارد المشروعة. نوصي باستخدام Content-Security-Policy-Report-Only أثناء مرحلة التطوير لمراقبة تقارير الانتهاكات وتشديد السياسة تدريجيًّا.

📖 ملخص

📝 تمارين

  1. قم بتنفيذ واجهة التسجيل. يجب ألا يقل طول كلمة المرور عن 8 أحرف وأن تحتوي على رقم واحد على الأقل. ويجب تشفيرها باستخدام bcrypt وتخزينها في قاعدة البيانات.
  2. تنفيذ واجهة برمجة التطبيقات (API) الخاصة بتسجيل الدخول لإصدار توقيع JWT صالح لمدة ساعة واحدة بعد التحقق من كلمة المرور؛ ويجب أن تتضمن الحمولة userId وrole.
  3. كتابة البرنامج الوسيط authenticate لاستخراج JWT من Authorization: Bearer <token> والتحقق من صحته
  4. اكتب برنامج الوسيط authorize('admin') لتقييد الوصول إلى DELETE /api/users/:id على المستخدمين الذين يحملون دور «admin» فقط.
  5. دمج Helmet في المشروع، وتهيئة CSP بحيث لا يُسمح بتحميل سوى البرامج النصية وأنماط التصميم من نفس المصدر، واستخدام curl للتحقق من التغييرات في رؤوس الاستجابة

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%