أمن واجهات API في .NET 5: المصادقة والتفويض باستخدام JWT خطوة بخطوة
مقدمة: لماذا يُعد أمن واجهات API أولوية قصوى؟
تعاني كثير من واجهات API من ثغرات أمنية خطيرة بسبب غياب المصادقة الصحيحة أو ضعف التفويض، وأحياناً بسبب غيابهما معاً. يظن بعض المطورين أن الخطر محدود لأن هذه الواجهات ليست متاحة للعامة، لكن هذا الافتراض غير دقيق؛ فالواجهات الداخلية أيضاً قد تكون هدفاً سهلاً إذا لم تُحمَ بشكل سليم.
في هذا الدليل العملي سنبني نموذجاً مبسطاً يوضح الفرق بين Authentication والمقصود بها التحقق من هوية المستخدم، وبين Authorization التي تحدد ما الذي يمكنه الوصول إليه بعد تسجيل الدخول. سنستخدم مشروعاً تخيلياً لوكالة تحقيق، بحيث يوجد مسؤول Admin يمكنه إصدار شارات رقمية للوكلاء Agent وتحديد مستوى التصريح الأمني الخاص بهم.
- المستوى
ClearanceLevel 1يتيح الوصول إلى الملفات العامة. - المستوى
ClearanceLevel 2يتيح الوصول إلى الملفات العامة والملفات المصنفة.
كيف تعمل المصادقة Authentication؟
تخيل أن الوكيل اجتاز جميع المتطلبات، وأصبح جاهزاً للتسجيل. يقدم مستنداته أولاً، وبعد التحقق منها يحصل على شارة تعريف. في التطبيقات البرمجية، تماثل هذه العملية تسجيل الدخول؛ إذ يقدّم المستخدم بياناته، وبعد التحقق منها يحصل على رمز وصول Token.
هذه العملية هي المصادقة Authentication، وهي المسؤولة عن التأكد من أن المستخدم هو بالفعل من يدّعيه.
في هذا المقال سنستخدم رموز JWT Bearer Tokens. وهي رموز يصدرها الخادم Server وتحتوي عادةً على معلومات مهمة مثل الادعاءات Claims والأدوار Roles المرتبطة بالمستخدم.
كيف يعمل التفويض Authorization؟
بعد حصول الوكيل على الشارة، يمكنه دخول المبنى والوصول إلى بعض الموارد. لكن إذا حاول فتح ملف مصنف دون صلاحية مناسبة، فسيتم رفض الطلب. هنا يأتي دور التفويض Authorization، إذ يحدد ما الذي يُسمح للمستخدم بالوصول إليه وما الذي يُمنع عنه.
يعتمد الخادم على البيانات المضمّنة داخل رمز JWT، مثل Claims وRoles، لاتخاذ قرار السماح أو المنع عند طلب مورد خاص.
تدفق الوصول إلى الموارد المحمية

يوضح المخطط السابق الفكرة الأساسية:
- يسجل المستخدم الدخول عبر واجهة
API. - يعيد الخادم رمز
Bearer Tokenصالحاً. - يرسل العميل
Clientهذا الرمز مع الطلبات اللاحقة. - يتحقق الخادم من الرمز والصلاحيات قبل منح الوصول إلى المورد المطلوب.
وهذان المفهومان، أي المصادقة والتفويض، هما جوهر ما سنطبقه عملياً في الخطوات التالية.
إعداد مشروع .NET 5 Web API
إنشاء المشروع الأساسي
ابدأ بإنشاء مشروع جديد عبر سطر الأوامر باستخدام الأمر التالي:
dotnet new webapi --name FBI
سينشئ هذا الأمر مشروعاً افتراضياً يحتوي على مثال WeatherForecast. وبما أننا سنبني مثالنا الخاص، يمكنك حذف الملف WeatherForecast.cs.
تثبيت الحزم المطلوبة
ثبّت الاعتمادات اللازمة لدعم المصادقة باستخدام JWT عبر الأوامر التالية:
dotnet add package Microsoft.IdentityModel.Tokens --version 6.9.0
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 5.0.4
تهيئة التحقق من الرمز داخل Startup.cs
داخل الدالة ConfigureServices في الملف Startup.cs، أضف إعدادات التحقق من الرمز كما يلي:
var TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = "https://fbi-demo.com",
ValidAudience = "https://fbi-demo.com",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
"SXkSqsKyNUyvGbnHs7ke2NCq8zQzNLW7mPmHbnZZ")),
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
هذه الإعدادات تحدد القواعد التي سيستخدمها التطبيق للتحقق من أي رمز JWT وارد. انتبه جيداً إلى أن طول المفتاح المستخدم في SymmetricSecurityKey يجب أن يكون مناسباً، ويفضل ألا يقل عن 32 حرفاً لضمان مستوى أمني جيد.
إضافة خدمة المصادقة
بعد ذلك، فعّل المصادقة في خدمات التطبيق بالشكل التالي:
services
.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.TokenValidationParameters = TokenValidationParameters;
});
تقوم الدالة AddAuthentication بتسجيل خدمات المصادقة داخل التطبيق، كما تضبط JWT Bearer Authentication كمخطط افتراضي. أما الدالة AddJwtBearer فتفعّل التحقق من رموز JWT بالاعتماد على الإعدادات التي حددناها في TokenValidationParameters.
إعداد سياسات التفويض Authorization Policies
الخطوة التالية هي تعريف السياسات التي سيتحقق منها التطبيق عند الوصول إلى الموارد:
services.AddAuthorization(cfg =>
{
cfg.AddPolicy("Admin", policy => policy.RequireClaim("type", "Admin"));
cfg.AddPolicy("Agent", policy => policy.RequireClaim("type", "Agent"));
cfg.AddPolicy("ClearanceLevel1", policy => policy.RequireClaim("ClearanceLevel", "1", "2"));
cfg.AddPolicy("ClearanceLevel2", policy => policy.RequireClaim("ClearanceLevel", "2"));
});
تُستخدم الدالة AddAuthorization لتسجيل خدمات التفويض، بينما تُعرّف الدالة AddPolicy القواعد الخاصة بكل نوع وصول.
في هذا المثال:
- سياسة
Adminتسمح فقط لمن يحمل الادعاءtype=Admin. - سياسة
Agentتسمح فقط لمن يحمل الادعاءtype=Agent. - سياسة
ClearanceLevel1تقبل المستويين1و2لأن المستوى الأعلى يجب أن يتمكن من الوصول إلى الموارد الأدنى. - سياسة
ClearanceLevel2تقبل المستوى2فقط.
الادعاء Claim هو زوج من الاسم والقيمة يصف المستخدم أو صلاحياته بشكل مباشر.
تفعيل المصادقة في خط الأنابيب
داخل الدالة Configure، أضف السطر التالي قبل app.UseAuthorization();:
app.UseAuthentication();
هذه الخطوة ضرورية، لأن التطبيق لن يتمكن من قراءة الرمز والتحقق منه قبل تطبيق سياسات التفويض ما لم تُفعَّل المصادقة أولاً.
إنشاء المتحكم AdminController
أعد تسمية الملف WeatherForecastController.cs إلى AdminController.cs، ثم عدّل محتواه ليصبح كالتالي:
using Microsoft.AspNetCore.Mvc;
namespace FBI.Controllers
{
[ApiController]
[Route("[controller]")]
public class AdminController : ControllerBase
{
public AdminController()
{
}
}
}
هذا المتحكم سيكون مسؤولاً عن تسجيل دخول المسؤول وإصدار الشارات الرقمية للوكلاء.
بناء واجهة تسجيل الدخول للمسؤول
لننشئ واجهة Login تتيح للمسؤول الحصول على رمز يمكنه من تنفيذ العمليات الحساسة:
[HttpPost]
[Route("[action]")]
public IActionResult Login([FromBody] User User)
{
// TODO: Authenticate Admin with Database
// If not authenticate return 401 Unauthorized
// Else continue with below flow
var Claims = new List<Claim>
{
new Claim("type", "Admin"),
};
var Key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
"SXkSqsKyNUyvGbnHs7ke2NCq8zQzNLW7mPmHbnZZ"));
var Token = new JwtSecurityToken(
"https://fbi-demo.com",
"https://fbi-demo.com",
Claims,
expires: DateTime.Now.AddDays(30.0),
signingCredentials: new SigningCredentials(Key, SecurityAlgorithms.HmacSha256)
);
return new OkObjectResult(new JwtSecurityTokenHandler().WriteToken(Token));
}
في هذا المثال، يمثل User نموذجاً يحتوي على خاصيتي Username وPassword. لاحظ أن التحقق الحقيقي من بيانات المستخدم يجب أن يتم عبر قاعدة بيانات أو خدمة هوية موثوقة، وليس كما في المثال التوضيحي فقط.
بعد نجاح التحقق، يتم إنشاء كائن من النوع JwtSecurityToken باستخدام نفس الإعدادات المعرفة مسبقاً، ثم تحويله إلى نص وإرجاعه داخل OkObjectResult.

يمكنك الآن فتح Swagger وتنفيذ واجهة تسجيل الدخول للحصول على رمز Bearer Token. احتفظ بهذا الرمز لأننا سنستخدمه في الخطوة التالية.
إنشاء واجهة إصدار الشارة GenerateBadge
إصدار شارة لوكيل جديد عملية حساسة، لذلك يجب أن تكون محمية بسياسة تسمح للمسؤول فقط:
[HttpPost]
[Route("[action]")]
[Authorize(Policy = "Admin")]
public IActionResult GenerateBadge([FromBody] Agent Agent)
{
var Claims = new List<Claim>
{
new Claim("type", "Agent"),
new Claim("ClearanceLevel", Agent.ClearanceLevel.ToString()),
};
var Key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
"SXkSqsKyNUyvGbnHs7ke2NCq8zQzNLW7mPmHbnZZ"));
var Token = new JwtSecurityToken(
"https://fbi-demo.com",
"https://fbi-demo.com",
Claims,
expires: DateTime.Now.AddDays(30.0),
signingCredentials: new SigningCredentials(Key, SecurityAlgorithms.HmacSha256)
);
return new OkObjectResult(new JwtSecurityTokenHandler().WriteToken(Token));
}
النموذج Agent يحتوي هنا على الخاصيتين Name من النوع string وClearanceLevel من النوع int.
عند محاولة استدعاء هذه الواجهة دون تمرير الرمز الخاص بالمسؤول، ستحصل على الاستجابة 401 Unauthorized، وهذا سلوك صحيح لأن الوصول غير مصرح به بدون مصادقة.
تفعيل زر Authorize في Swagger
لتمرير الرمز بسهولة عبر Swagger، عدّل إعدادات services.AddSwaggerGen كما يلي:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "FBI", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please enter JWT with Bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
بعد تحديث الصفحة في المتصفح، ستلاحظ ظهور زر Authorize أعلى قائمة الواجهات. اضغط عليه، ثم اكتب أولاً الكلمة Bearer وبعدها مسافة ثم الرمز الناتج من الواجهة /Admin/Login. بعد تأكيد العملية، ستتم إضافة الترويسة Authorization تلقائياً إلى الطلبات.
الآن عند استدعاء الواجهة GenerateBadge مجدداً، ستحصل على رمز جديد يمثل شارة الوكيل. احفظ هذا الرمز، ويفضل في هذه المرحلة أن يكون ClearanceLevel مساوياً لـ 1.
إنشاء المتحكم AgentController
أنشئ ملفاً جديداً باسم AgentController.cs بالمحتوى التالي:
using Microsoft.AspNetCore.Mvc;
namespace FBI.Controllers
{
[ApiController]
[Route("[controller]")]
[Authorize(Policy = "Agent")]
public class AgentController : ControllerBase
{
public AgentController()
{
}
}
}
بتطبيق السمة Authorize على مستوى المتحكم بالكامل، نضمن أن كل الواجهات داخله لن تكون متاحة إلا للوكلاء الذين يحملون الادعاء type=Agent. حتى المسؤول نفسه لن يتمكن من استخدامها إذا كان رمزه لا يحتوي على هذا الادعاء.
إضافة واجهات الوصول إلى الملفات
الآن لنضف الواجهات الخاصة بالملفات العامة والمصنفة:
[HttpGet]
[Route("[action]")]
[Authorize(Policy = "ClearanceLevel1")]
public ActionResult<String> AccessPublicFiles()
{
return new OkObjectResult("Public Files Accessed");
}
[HttpGet]
[Route("[action]")]
[Authorize(Policy = "ClearanceLevel2")]
public ActionResult<String> AccessClassifiedFiles()
{
return new OkObjectResult("Classified Files Accessed");
}
هنا قمنا بتقسيم الصلاحيات بوضوح:
- الواجهة
AccessPublicFilesمتاحة لمن يملك السياسةClearanceLevel1، أي المستوى1أو2. - الواجهة
AccessClassifiedFilesمتاحة فقط لمن يملك السياسةClearanceLevel2.
إذا حاولت استخدام رمز المسؤول للوصول إلى هذه الواجهات، فستحصل على الخطأ 403 Forbidden لأن المستخدم مصادق عليه، لكنه غير مخوّل للوصول إلى هذا المورد.
لذلك سجّل الخروج من نافذة Authorize في Swagger، ثم أدخل رمز الوكيل الذي حصلت عليه من الواجهة GenerateBadge بعد إضافة البادئة Bearer.
عند استدعاء /Agent/AccessPublicFiles باستخدام رمز وكيل بمستوى 1، ستكون النتيجة 200 OK مع الرسالة Public Files Accessed. أما عند محاولة الوصول إلى /Agent/AccessClassifiedFiles، فستظهر استجابة 403 Forbidden لأن مستوى التصريح لا يكفي.
تحديث مستوى التصريح الأمني
لنفترض أن أداء الوكيل تطور مع الوقت، وتمت ترقيته إلى المستوى ClearanceLevel 2. في هذا السيناريو:
- يسجل المسؤول الدخول عبر
/Admin/Loginللحصول على رمز جديد. - يستخدم رمز المسؤول في
Swagger Authorize. - يستدعي الواجهة
/Admin/GenerateBadgeمع قيمةClearanceLevel = 2. - يستلم الوكيل الرمز الجديد ويستخدمه في طلباته.
- عند استدعاء
/Agent/AccessClassifiedFilesسيحصل على النتيجةClassified Files Accessed.
هذا المثال يوضح بصورة عملية كيف يمكن للرمز نفسه أن يحمل الصلاحيات، وكيف يغيّر تحديث Claims من سلوك الوصول دون الحاجة إلى تعديل منطق كل واجهة على حدة.
أفضل ممارسات مهمة لتأمين واجهات API
المثال السابق تعليمي ومفيد، لكنه لا يكفي وحده في بيئات الإنتاج. إذا كنت تستهدف بناء نظام قوي ومتوافق مع متطلبات الأمان الحديثة، فاحرص على ما يلي:
- عدم تخزين المفتاح السري داخل الشيفرة المصدرية مباشرة، بل وضعه في إعدادات آمنة أو في خدمة إدارة أسرار.
- الاعتماد على مدة صلاحية قصيرة للرموز الحساسة مع استخدام آلية
Refresh Tokenعند الحاجة. - تشفير الاتصال دائماً عبر
HTTPS. - تسجيل محاولات الدخول الفاشلة والعمليات الحساسة ضمن سجلات تدقيق
Audit Logs. - تطبيق حدود على عدد الطلبات
Rate Limitingلتقليل فرص الاستغلال والهجمات الآلية. - تقييد الوصول حسب
IPأو النطاقDomainإذا كانت بنية النظام تسمح بذلك. - التحقق من صلاحية المستخدم من قاعدة البيانات عند تنفيذ العمليات الحرجة، وعدم الاكتفاء بالرمز في بعض السيناريوهات الحساسة.
لماذا يفيد هذا النهج في القابلية للتوسع؟
استخدام JWT مع سياسات Authorization يمنحك بنية مرنة وسهلة التوسع. يمكنك إضافة أدوار جديدة أو مستويات تصريح إضافية دون إعادة كتابة منطق الأمان كاملاً. كما أن توزيع الصلاحيات على شكل سياسات يجعل الكود أوضح وأسهل في الصيانة والاختبار.
ومن منظور هندسي، فإن الفصل بين المصادقة والتفويض يسهّل دمج النظام لاحقاً مع بوابات هوية مثل Identity Server أو مزودات خارجية، مع الحفاظ على نفس فلسفة حماية الموارد.
الخلاصة التقنية
تأمين واجهات API ليس ميزة إضافية، بل جزء أساسي من جودة النظام وموثوقيته. في .NET 5 يوفّر الدمج بين JWT Bearer Authentication وسياسات Authorization طريقة عملية وواضحة للتحكم في الهوية والصلاحيات. تقنياً، أفضل ما في هذا النهج هو بساطته وقابليته للتوسع، لكن نجاحه الحقيقي يعتمد على حسن إدارة المفاتيح، وضبط مدة صلاحية الرموز، وتطبيق طبقات أمان إضافية في بيئة الإنتاج.