تعلم برمجة الـ RESTful services أو الـ Web APIs بإستخدام ASP.NET Core بطريقة مفصلة وتدريجية
يعتبر الـ Basic Authentication من أبسط أنواع المصادقة authentication التي يمكن تطبيقها في مشروعك ولكنها ليست آمنه بالقدر الكافي حيث أنه يتم تمرير إسم المستخدم وكلمة المرور في كل طلب request بطريقة مكشوفة ولكن مشفرة الى base64 والتي يمكن فك تشفيرها ببساطة.
وهي تعتبر حل سريع ومقبول في الشبكات الداخلية بشرط أن تكون قناة الإتصال HTTPS. والفكرة تتمحور حول إرسال header بإسم Authorization متبوع بمسافة ثم كلمة Basic ثم مسافة وبعد ذلك إسم المستخدم وكلمة المرور وبينهما علامة : وجميعها محول الى نص من نوع base64. أي بالشكل التالي:
Authorization: Basic base64("username:password")
هنالك طريقتان لتنفيذ الـ basic authentication، إما عن طريق الـ middleware أو عن طريق الـ handler وهو الإسلوب الذي سنتبعة حيث إنه يعتبر أكثر مرونه.
ننشئ branch جديد ونتحول اليه:
git branch basic-auth
git checkout basic-auth
ننشئ مجلد جديد بإسم Users وبداخله ملف بإسم User.cs يمثل المستفيد من الخدمة التي نقدمها:
namespace aspnetcorewebapiproject.Users
{
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
}
أيضاً ننشئ interface يمثل عملية التحقق من المستخدم والتي ممكن أن تكون من قاعدة بيانات أو Active Directory أو خدمة أخرى على سبيل المثال. سيكون إسم الملف IAuthenticationService.cs وبه عمليه وحدة:
using System.Threading.Tasks;
namespace aspnetcorewebapiproject.Users
{
public interface IAuthenticationService
{
Task<User> Authenticate(string username, string password);
}
}
بإمكاننا هنا إضافة جميع العمليات المتعلقة بإدارة المستخدمين من إضافة وتعديل بيانات ورقم سري … الخ.
ننشئ الآن ملف جديد يعتبر تتطبيق implementation للـ interface السابق بإسم AuthenticationService.cs:
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace aspnetcorewebapiproject.Users
{
public class AuthenticationService : IAuthenticationService
{
private List<User> _users = new List<User>
{
new User { Id = 1, Username = "ahmad", Password = "abc@123" },
new User { Id = 2, Username = "ali", Password = "def@456" },
new User { Id = 3, Username = "yousef", Password = "ghi@789" }
};
public async Task<User> Authenticate(string username, string password)
{
var user = await Task.Run(() => _users.SingleOrDefault(x => x.Username == username && x.Password == password));
// user not found
if (user == null)
return null;
// return user details without password
user.Password = string.Empty;
return user;
}
}
}
أخترنا هنا أن نحفظ بيانات المستخدمين في قائمة list في الذاكرة لتسهيل العملية ولكن عملياً يجب أن تكون بيانات المستخدمين محفوظة في نظام آخر.
والآن جاء دور إخبار بيئة العمل بأنه متى ما تم طلب IAuthenticationService أعد AuthenticationService وذلك في الدالة ()ConfigureServices في Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<Users.IAuthenticationService, Users.AuthenticationService>();
}
ننشئ مجلد جديد بإسم Handlers وبداخله ملف بإسم BasicAuthenticationHandler.cs وهي التي ستحدد طريقة المصادقة:
using System;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using aspnetcorewebapiproject.Users;
namespace aspnetcorewebapiproject.Handlers
{
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly Users.IAuthenticationService _authService;
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
Users.IAuthenticationService authService)
: base(options, logger, encoder, clock)
{
// 1. Pass auth service implementation
_authService = authService;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// 2. Make sure Authorization key exists
if (!Request.Headers.ContainsKey("Authorization"))
return AuthenticateResult.Fail("Missing Authorization Header");
User user = null;
try
{
// 3. Key exists. Extract username and password and authenticate the pair
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
if(!"Basic".Equals(authHeader.Scheme, StringComparison.OrdinalIgnoreCase))
{
// 4. Not Basic authentication header
return AuthenticateResult.Fail("Not Basic authentication header");
}
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
var username = credentials[0];
var password = credentials[1];
user = await _authService.Authenticate(username, password);
}
catch
{
// 5. Key exists but there was an error with the extraction or authentication
return AuthenticateResult.Fail("Invalid Authorization Header");
}
// 6. Could not find user with username/password
if (user == null)
return AuthenticateResult.Fail("Invalid Username or Password");
// 7. User found. Create ticket for user
var claims = new[] {
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
}
سيتم إستدعاء HandleAuthenticateAsync قبل كل عملية إستدعاء request للخدمة.
وفيما يلي شرح لأهم النقاط التي وردت في الكود السابق:
نمرر Users.IAuthenticationService في الـ constructor والتي سيتم إستبدالها بالتطبيق AuthenticationService
في حالة لم يتم تمرير Authorization في الـ HTTP Header فإن عملية التحقق ستفشل
نستخلص قيمة الـ Authorization header
نتأكد أن نوع عملية التحقق هي Basic
إذا حدث خطأ في إستخلاص الإسم وكلمة المرور فإن العملية ستفشل
إذا لم نجد المستخدم الذي يحمل هذا الإسم وكلمة المرور فإن التحقق سيفشل
في حالة أننا وجدنا المستخدم سنقوم بحفظ معلوماته في ما يسمى بالـ claims وهي عبارة عن key-value pair تمثل بيانات المستخدم
لتفعيل ما قمنا به نقوم بالتالي:
علينا إضافة ()app.UseAuthentication قبل ()app.UseAuthorization:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger, IApiVersionDescriptionProvider provider)
{
...
app.UseAuthentication();
app.UseAuthorization();
...
}
نقوم بإضافة عملية التحقق ونعتمد التطبيق الموجود في BasicAuthenticationHandler:
public void ConfigureServices(IServiceCollection services)
{
...
// configure DI for application services
services.AddScoped<Users.IAuthenticationService, Users.AuthenticationService>();
// configure basic authentication
services.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
}
لجعل EmployeesController تتطلب التحقق من المستخدم قبل إمكانية الإستفادة منها فإنه يتوجب علينا إضافة الـ [Authorize] attribute:
[Authorize]
...
public class EmployeesController : ControllerBase
{
...
سنقوم بطلب الخدمة هذه المرة بدون تمرير الـ Authorization header:
![]() |
نلاحظ أن الخدمة رفضت هذه العملية وأعادت الـ HTTP Status Code التالي: Unauthorized 401.
والآن إختر التبويب Authorization، ثم في القائمة المنسدلة TYPE إختر Basic Auth وفي Username/Password في اليمين ضع إسم المستخدم وكلمة المرور لأحد المستخدمين المسجلين في AuthenticationService:
![]() |
ولترى الـ header الذي سيتم إرساله، إختر التبويب Headers ثم إضغط على hidden:
![]() |
ستلاحظ أن القيمة سيتم إرسالها بالطريقة التي ذكرناها سابقاً:
![]() |
وعند التنفيذ سترى أنه بإمكاننا الآن الوصول الى الخدمة:
![]() |
بإمكانك الآن إستخدام User.Identity للحصول على معلومات المستخدم الذي قام بالإستدعاء. وعلى سبيل المثال:
public async Task<ActionResult<EmployeesResponse<EmployeeDetailsDto>>> GetEmployee(int id)
{
_logger.LogInformation($"GetEmployee requested by: {User.Identity.Name}");
...
![]() |
هناك حالات لا تريد فيها المستخدم أن يرسل إسم مستخدم وكلمة المرور، كأن يسجل في الخدمة لأول مرة، ففي هذه الحالة يمكنك إضافة الـ attribute التالي: [AllowAnonymous] على العملية التي لا تتطلب التحقق من المستخدم.
نضيف الآن هذه التعديلات الى git:
git add .
git commit -m "adds basic authentication support"
سوف نبقي هذه الخاصية في هذا الـ branch وسوف نعود الى master من دونها لنتمكن من تكملة باقي الدروس:
git checkout master
ASP.NET Core 3.1 - Basic Authentication Tutorial with Example API
ASP.NET Core Web API + Entity Framework Core : Basic Authentication Explained - EP07