تأمين واجهة swagger في production في ASP.Net Core net5.0 - Securing swagger UI in production in ASP.Net Core net5.0

تطوير الويب Backend - ASP.NET Core API

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

بنستخدم في هذه الطريقة أليات المصادقة authentications mechanism لحماية swagger API. 

تمام نفرض انو عندك مشروع API جاهز وانت فقط بحاجه الى تأمين API. 

الفكرة: 
بنعمل class لإضافة authentications وفي هذا المثال ثبتنا الكود الخاص باسم المستخدم وكلمة المرور(اكيد ممكن تعمل تطوير للطريقة وقراءة هذه البيانات من قاعدة البيانات مثلا). لكن الان في هذه المرحلة ثبتنا هذه البيانات في الكود. 
أولا نضيف مجلد باسم Helpers في المشروع وبعدها نضيف class باسم SwaggerBasicAuthMiddleware   بالشكل التالي: 

الكود الخاص بهذا class هو : 

public class SwaggerBasicAuthMiddleware
    {
        private readonly RequestDelegate next;
        public SwaggerBasicAuthMiddleware(RequestDelegate next)
        {
            this.next = next;
        }
        public async Task InvokeAsync(HttpContext context)
        {
            if (context.Request.Path.StartsWithSegments("/swagger"))
            {
                string authHeader = context.Request.Headers["Authorization"];
                if (authHeader != null && authHeader.StartsWith("Basic "))
                {
                    // Get the credentials from request header
                    var header = AuthenticationHeaderValue.Parse(authHeader);
                    var inBytes = Convert.FromBase64String(header.Parameter);
                    var credentials = Encoding.UTF8.GetString(inBytes).Split(':');
                    var username = credentials[0];
                    var password = credentials[1];
                    // validate credentials
                    if (username.Equals("Layan")
                      && password.Equals("Lareen"))
                    {
                        await next.Invoke(context).ConfigureAwait(false);
                        return;
                    }
                }
                context.Response.Headers["WWW-Authenticate"] = "Basic";
                context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            }
            else
            {
                await next.Invoke(context).ConfigureAwait(false);
            }
        }
    }

نوخذ فكره سريعة عن الكود:

عرفنا في البداية متغير من النوع RequestDelegate الي من خلالو بتم معالجة الطلبات من نوع http وبعدها عملنا injection لها المتغير في constructor 

الكود: 



private readonly RequestDelegate next;
        public SwaggerBasicAuthMiddleware(RequestDelegate next)
        {
            this.next = next;
        }
بعدها انشاءنا function واستقبلنا فيه متغير من نوع HttpContext وبعدها رجعنا Path الخاص ب swagger. بعد هيك تم التأكد من Header إذا في طلب او لا باستخدام الكود: 

string authHeader = context.Request.Headers["Authorization"];

بعد هيك نتأكد من header اذا ما كان null او كان اذا كان يبدأ ب Basic . وبعد هيك قرأنا البيانات من Header وتأكدنا من اسم المستخدم وكلمة المرور. اذا كانت النتيجة صحيحه بتم عرض الصفحة باستخدام الكود: 

await next.Invoke(context).ConfigureAwait(false);
اذا كانت القيم غير صحيحه بتم ارجاع خطأ باستخدام الكود:

 context.Response.Headers["WWW-Authenticate"] = "Basic";
 context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;

تمام الان بنعمل extension method لاستخدام هذه الكود
اضف class جديد باسم  SwaggerAuthorizeExtensions وبنضيف الكود التالي :

  public static class SwaggerAuthorizeExtensions
    {
        public static IApplicationBuilder UseSwaggerAuthorized(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<SwaggerBasicAuthMiddleware>();
        }
    }
اضافنا function باسم UseSwaggerAuthorized واضفنا الكود التالي :

return builder.UseMiddleware<SwaggerBasicAuthMiddleware>();

في هذا function تم استدعاء class السابق SwaggerBasicAuthMiddleware

تمام الان نطبق هذا الحكي في startup.cs

بنضيف الكود التالي في  Configure في ملف startup.cs 

app.UseAuthorization();
app.UseSwaggerAuthorized();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "SecureSwagger v1"));

من المهم الترتيب في تطبيق الكود السابق تأكد من إضافة ()UseSwaggerAuthorized  قبل استدعاء ()UseSwagger  و ()UseSwaggerUI  ، الهدف من هذي الحركة هو استدعاء middleware المسؤول عن التحقق من اسم المستخدم وكلمة المرور قبل الوصول إلى واجهة المستخدم swagger ui.

شغل الصفحة واكيد بتكون النتيجة كما في الصورة.
اذا تم ادخال بيانات خطأ بتم الرجاع خطأ من نوع : 401 error page 

الكود الكامل هنا. 

SwaggerBasicAuthMiddleware class
public class SwaggerBasicAuthMiddleware
    {
        private readonly RequestDelegate next;
        public SwaggerBasicAuthMiddleware(RequestDelegate next)
        {
            this.next = next;
        }
        public async Task InvokeAsync(HttpContext context)
        {
            if (context.Request.Path.StartsWithSegments("/swagger"))
            {
                string authHeader = context.Request.Headers["Authorization"];
                if (authHeader != null && authHeader.StartsWith("Basic "))
                {
                    // Get the credentials from request header
                    var header = AuthenticationHeaderValue.Parse(authHeader);
                    var inBytes = Convert.FromBase64String(header.Parameter);
                    var credentials = Encoding.UTF8.GetString(inBytes).Split(':');
                    var username = credentials[0];
                    var password = credentials[1];
                    // validate credentials
                    if (username.Equals("Layan")
                      && password.Equals("Lareen"))
                    {
                        await next.Invoke(context).ConfigureAwait(false);
                        return;
                    }
                }
                context.Response.Headers["WWW-Authenticate"] = "Basic";
                context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            }
            else
            {
                await next.Invoke(context).ConfigureAwait(false);
            }
        }
    }

SwaggerAuthorizeExtensions class

   public static class SwaggerAuthorizeExtensions
    {
        public static IApplicationBuilder UseSwaggerAuthorized(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<SwaggerBasicAuthMiddleware>();
        }
    }
Configure function 

  app.UseAuthentication();
            app.UseSwaggerAuthorized();
            app.UseSwagger();
            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "DietWrold.API v1"));