مطالب
غیرمعتبر کردن توکن و یا کوکی سرقت شده در برنامه‌های مبتنی بر ASP.NET Core
چند روز قبل، یکی از کانال‌های فنی معروف یوتیوب با بیش از 15 میلیون مشترک، هک و پاک شد! که داستان آن‌را در اینجا می‌توانید پیگیری کنید. در این هک، مهاجم در سعی اول، پیشنهاد پشتیبانی مالی از شبکه را داده و در ایمیل دوم، پس از جلب اعتماد اولیه، یک فایل به ظاهر PDF مفاد قرارداد را ارسال کرده که با کلیک بر روی آن، تمام کوکی‌های یوتیوب مالک کانال، سرقت و مورد سوء استفاده قرار گرفته! در یک چنین حالتی، مهم نیست که شما اعتبارسنجی دو مرحله‌ای را فعال کرده‌اید و یا از بهترین روش‌های رمزنگاری برای امن کردن اطلاعات کوکی و یا توکن خود استفاده کرده‌اید، همینقدر که اصل محتوای کوکی و یا توکن شما در اختیار شخص دیگری قرار گیرد، می‌تواند بدون نیاز به لاگین و دانستن کلمه‌ی عبور شما، بجای شما وارد سیستم شده و تغییرات دلخواهی را اعمال کند!
بنابراین سؤال اینجاست که ما (توسعه دهندگان) چگونه می‌توانیم یک چنین حملاتی را مشکل‌تر کنیم؟ در این مطلب روشی را در جهت سعی در غیرمعتبر کردن توکن‌ها و یا کوکی‌های سرقت شده، در برنامه‌های مبتنی بر ASP.NET Core بررسی خواهیم کرد.


توسعه‌ی یک سرویس تشخیص مرورگر و سیستم عامل شخص وارد شده‌ی به سیستم

یکی از روش‌های غیرممکن کردن یک چنین حملاتی، درج مشخصات سیستم عامل و مرورگر شخص وارد شده‌ی به سیستم، در کوکی و همچنین توکن صادر شده‌ی حاصل از اعتبارسنجی موفق است. سپس زمانیکه قرار است از اطلاعات این کوکی و یا توکن در برنامه استفاده شود، این اطلاعات را با اطلاعات درخواست جاری کاربر مقایسه کرده و در صورت عدم تطابق، درخواست او را برگشت می‌زنیم. برای مثال اگر عملیات لاگین، در ویندوز انجام شده و اکنون توکن و یا کوکی حاصل، در سیستم عامل اندروید در حاصل استفاده‌است، یعنی ... این عملیات مشکوک است و باید خاتمه یابد و کاربر باید مجبور به لاگین مجدد شود و نه اعتبارسنجی خودکار بدون زحمت!
برای این منظور می‌توان از کتابخانه‌ی UA-Parser استفاده کرد و توسط آن سرویس زیر را توسعه داد:
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
using UAParser;

namespace ASPNETCore2JwtAuthentication.Services;

/// <summary>
///     To invalidate an old user's token from a new device
/// </summary>
public class DeviceDetectionService : IDeviceDetectionService
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ISecurityService _securityService;

    public DeviceDetectionService(ISecurityService securityService, IHttpContextAccessor httpContextAccessor)
    {
        _securityService = securityService ?? throw new ArgumentNullException(nameof(securityService));
        _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
    }

    public string GetCurrentRequestDeviceDetails() => GetDeviceDetails(_httpContextAccessor.HttpContext);

    public string GetDeviceDetails(HttpContext context)
    {
        var ua = GetUserAgent(context);
        if (ua is null)
        {
            return "unknown";
        }

        var client = Parser.GetDefault().Parse(ua);
        var deviceInfo = client.Device.Family;
        var browserInfo = $"{client.UA.Family}, {client.UA.Major}.{client.UA.Minor}";
        var osInfo = $"{client.OS.Family}, {client.OS.Major}.{client.OS.Minor}";
        //TODO: Add the user's IP address here, if it's a banking system.
        return $"{deviceInfo}, {browserInfo}, {osInfo}";
    }

    public string GetDeviceDetailsHash(HttpContext context) =>
        _securityService.GetSha256Hash(GetDeviceDetails(context));

    public string GetCurrentRequestDeviceDetailsHash() => GetDeviceDetailsHash(_httpContextAccessor.HttpContext);

    public string GetCurrentUserTokenDeviceDetailsHash() =>
        GetUserTokenDeviceDetailsHash(_httpContextAccessor.HttpContext?.User.Identity as ClaimsIdentity);

    public string GetUserTokenDeviceDetailsHash(ClaimsIdentity claimsIdentity)
    {
        if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any())
        {
            return null;
        }

        return claimsIdentity.FindFirst(ClaimTypes.System)?.Value;
    }

    public bool HasCurrentUserTokenValidDeviceDetails() =>
        HasUserTokenValidDeviceDetails(_httpContextAccessor.HttpContext?.User.Identity as ClaimsIdentity);

    public bool HasUserTokenValidDeviceDetails(ClaimsIdentity claimsIdentity) =>
        string.Equals(GetCurrentRequestDeviceDetailsHash(), GetUserTokenDeviceDetailsHash(claimsIdentity),
                      StringComparison.Ordinal);

    private static string GetUserAgent(HttpContext context)
    {
        if (context is null)
        {
            return null;
        }

        return context.Request.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgent)
                   ? userAgent.ToString()
                   : null;
    }
}
توضیحات:
اصل کار این سرویس در متد زیر رخ می‌دهد:
    public string GetDeviceDetails(HttpContext context)
    {
        var ua = GetUserAgent(context);
        if (ua is null)
        {
            return "unknown";
        }

        var client = Parser.GetDefault().Parse(ua);
        var deviceInfo = client.Device.Family;
        var browserInfo = $"{client.UA.Family}, {client.UA.Major}.{client.UA.Minor}";
        var osInfo = $"{client.OS.Family}, {client.OS.Major}.{client.OS.Minor}";
        //TODO: Add the user's IP address here, if it's a banking system.
        return $"{deviceInfo}, {browserInfo}, {osInfo}";
    }
در اینجا با استفاده از کتابخانه‌ی UA-Parser، سعی می‌کنیم تا جزئیات مرورگر و سیستم عامل شخص را تهیه کنیم. سپس در قسمت دیگری از این سرویس، این اطلاعات را هش می‌کنیم. از این جهت که هم حجم آن کاهش یابد و بی‌جهت کوکی و یا توکن ما را حجیم نکند و هم بررسی محتوای آن جهت شبیه سازی آن، غیرممکن شود. هر مشخصات دریافتی در حین لاگین، همواره یک هش مشخص و یکتا را دارد. به همین جهت متدهای هش کردن اطلاعات را هم در اینجا مشاهده می‌کنید. به علاوه‌ی متد HasUserTokenValidDeviceDetails که کار آن، دریافت Claim مرتبط با این اطلاعات، از کوکی و یا توکن جاری و مقایسه‌ی آن با اطلاعات Http Request جاری است. اگر این دو یکی نبودند، یعنی احتمال سوء استفاده‌ی از اطلاعات شخص، وجود دارد.


اضافه کردن اطلاعات مشخصات دستگاه کاربر به کوکی و یا توکن او

همانطور که عنوان شد، در متد HasUserTokenValidDeviceDetails، ابتدا مشخصات دستگاه موجود در کوکی و یا توکن دریافتی، استخراج می‌شود. به همین جهت نیاز است این مشخصات را دقیقا در حین لاگین موفق، به صورت یک Claim جدید، برای مثال از نوع ClaimTypes.System به مجموعه‌ی Claims کاربر اضافه کرد:
new(ClaimTypes.System, _deviceDetectionService.GetCurrentRequestDeviceDetailsHash(),
ClaimValueTypes.String, _configuration.Value.Issuer),


یکپارچه کردن DeviceDetectionService با اعتبارسنج‌های کوکی‌ها و توکن‌ها

پس از افزودن مشخصات سیستم کاربر وارد شده‌ی به سیستم، به صورت یک Claim جدید به توکن‌ها، روش اعتبارسنجی اطلاعات موجود در توکن رسیده، در رخ‌داد گردان OnTokenValidated است که امکان دسترسی به HttpContext و محتوای توکن را میسر می‌کند:
.AddJwtBearer(cfg =>
{
      cfg.Events = new JwtBearerEvents
      {
           OnTokenValidated = context =>
           {
               var tokenValidatorService = context.HttpContext.RequestServices.GetRequiredService<ITokenValidatorService>();
              return tokenValidatorService.ValidateAsync(context);
           },
       };
  });
و یا اگر از کوکی‌ها استفاده می‌کنید، معادل آن به صورت زیر است:
.AddCookie(options =>
{
    options.Events = new CookieAuthenticationEvents
    {
       OnValidatePrincipal = context =>
       {
         var cookieValidatorService = context.HttpContext.RequestServices.GetRequiredService<ICookieValidatorService>();
         return cookieValidatorService.ValidateAsync(context);
       }
    };
});

در کل تمام تغییرات مورد نیاز مرتبط را جهت یک برنامه‌ی تولید کننده‌ی JWT در اینجا و برای یک برنامه‌ی مبتنی بر کوکی‌ها در اینجا می‌توانید مشاهده کنید.
نظرات مطالب
مدیریت کلیدهای کیبرد در جاوا اسکریپت
سوال خوبی است. قبل از پاسخ به سوال شما باید کمی درباره رویدادها در عناصر DOM توضیح داده بشه. 
در صفحات html به صورت پیش فرض، هر رویدادی که در عناصر زیردست یا فرزند رخ میدهد به ترتیب در تمام عناصر والدش انتشار می‌یابد. به این فرایند event propagation یا event bubble up میگویند.
در حالت عادی برای تغییر رفتار پیش فرض مدیریت رویدادهای عناصر DOM در مرورگرها، با استفاده از زبان جاوا اسکریپت چند روش وجود دارد.
مثلا آرگومان رویداد (همان متغیر معروف e) در مرورگرهای مدرن دو متد برای اینکار دارد:
e.preventDefault()
e.stopPropagation()
متد preventDefault همانطور که از نامش هم پیداست از اجرای رفتار پیش فرض رویداد جاری جلوگیری میکند. مثلا با قرار دادن این متد در رویداد مربوط به کلیک یک لینک، رفتار پیش فرض کلیک روی لینک (همان انتقال به آدرس تنظیم شده در پراپرتی src لینک) اجرا نمیشود.
متد stopPropagation هم همانطور که از نامش پیداست از انتشار رویداد جاری به عناصر والدش جلوگیری میکند.
راه حل دیگری که برای تغییر این رفتارهای پیش فرض وجود دارد استفاده از return flase در انتهای رویداد است. این کار تاثیری همانند استفاده از متد preventDefault دارد. تفاوت این دو روش آن است که برگشت مقدار false تنها میتواند در انتهای یک متد استفاده شود بنابراین اگر خطایی در متد مربوطه رخ دهد احتمال اجرا نشدن این خط و درنتیجه اجرای ادامه فرایند با اجرای رفتار پیش فرض رویداد مربوطه وجود دارد. اما متد preventDefault را میتوان در ابتدای متد رویداد استفاده کرد و خیال خود را راحت کرد!
نکته: دو متد اشاره شده در نسخه‌های قدیمی مرورگرها (مثلا IE 8 و قبلتر) وجود ندارد!
برای آزمایش تمامی این موارد میتوان از کد زیر که برای اینکار آماده کردم استفاده کرد:
<html>
<body>
<div onclick="document.body.appendChild(outter)" style='border:solid 1px'>
    Outer Div
    <a id="inner" href="index2.htm">index2</a>
</div>
<script>
var outter = document.createElement('div');
outter.innerText = 'outter';
var inner = document.createElement('div');
inner.innerText = 'inner';

document.getElementById('inner').onclick = function(e) {
    e = e || window.event;
    document.body.appendChild(inner);
    //e.preventDefault();
    //e.stopPropagation();
    //e.returnValue = false;
    return false;
};
</script>
</body>
</html>
البته درصورت استفاده از jQuery به دلیل اینکه این کتابخانه از نمونه ای سفارشی برای آرگومان رویدادها استفاده میکند فرایندهای موجود کمی تفاوت دارد.
اطلاعات بیشتر: ^ و ^
مطالب
کامپوننت‌های جنریک در Blazor
طرح مشکل! نیاز به دریافت انواع و اقسام مقادیر یک جنس (مانند اعداد و یا تاریخ) در کامپوننت‌های Blazor

فرض کنید می‌خواهید عددی را در کامپوننتی، دریافت کنید. می‌توان اینکار را با تعریف یک پارامتر عمومی به صورت زیر انجام داد:
[Parameter] public int Value { get; set; }
و ... مشکل از همینجا شروع می‌شود! خوب، برای نوع‌های double ، decimal ، float ، long و غیره چه باید کرد؟ آیا باید به ازای هر کدام، یک پارامتر مخصوص را تعریف کنیم؟ و یا اگر خواستیم زمان و تاریخ را از کاربر دریافت کنیم، آیا باید او را مجبور به ارائه‌ی فقط DateTime کنیم و یا ... شاید بهتر باشد به او بگوییم اگر «رشته‌ای» را در اختیار ما قرار دهید، ما یکبار دیگر خودمان کار تبدیل آن‌را به نوع تاریخ انجام خواهیم داد!
برای حل این نوع مشکلات، می‌توان در Blazor پارامترها را جنریک تعریف کرد. برای نمونه اگر به طراحی یک سری کامپوننت‌های پایه‌ای Blazor دقت کنیم، امکان دریافت مستقیم انواع و اقسام اعداد و یا انواع و اقسام نوع‌های مرتبط با زمان را دارند؛ بدون اینکه این اطلاعات را به صورت رشته‌ای دریافت کنند و یا به ازای هر نوع عددی، یک پارامتر جداگانه را تعریف و یا اینکه استفاده کننده را محدود به ورود تنها یک نوع عددی و یا زمانی خاص کرده باشند.


نحوه‌ی جنریک تعریف کردن یک کامپوننت در Blazor

Blazor فایل‌های razor. را در حین پروسه‌ی build، به cs. تبدیل می‌کند و از آنجائیکه فایل‌های razor. الزامی به تعریف نام کلاس مرتبط را ندارند و این نام به صورت خودکار دقیقا از نام خود فایل، دریافت می‌شود، نیاز است راهی را یافت تا بتوان کلاس مرتبط را به صورت Generic تعریف کرد. برای این منظور، دایرکتیو ویژه‌ای به نام typeparam@ پیش‌ بینی شده‌است که توسط آن می‌توان یک یا چند نوع/پارامتر جنریک جدا شده‌ی توسط کاما را تعریف کنیم (تعریف شده‌ی در فایل فرضی MyGenericComponent.razor):
@typeparam T

@code {
    [Parameter] public T Value { get; set; }
}
در این مثال کامپوننتی را مشاهده می‌کنید که مقدار دریافتی خود را به صورت جنریک از نوع T دریافت می‌کند. این T باید توسط دایرکتیو typeparam@ دقیقا قید شود تا در حین ساخت خودکار کلاس معادل این فایل، مورد استفاده قرار گیرد.


نحوه‌ی جنریک تعریف کردن کامپوننت‌های دارای code-behind

اگر قسمت code@ فایل‌های razor. را به فایل‌های code-benind منتقل می‌کنید و داخل فایل razor.، کد #C نمی‌نویسید، روش جنریک تعریف کردن یک کامپوننت، به صورت زیر خواهد بود (برای مثال دو فایل MyGenericComponentWithCodeBehind.razor و MyGenericComponentWithCodeBehind.razor.cs تعریف شده‌اند):
using Microsoft.AspNetCore.Components;

namespace BlazorGenericComponents.Pages;

public partial class MyGenericComponentWithCodeBehind<T>
{
    [Parameter] public T Value { get; set; }
}
در اینجا ذکر دایرکتیو typeparam@ در فایل razor. متناظر هم باید صورت گیرد:
@typeparam T
یعنی هر دو کلاس نهایی حاصل از این فایل‌ها که به صورت partial تعریف شده‌اند (و در آخر یکی می‌شوند)، باید به صورت جنریک تعریف شوند.


روش مقید کردن پارامترهای جنریک در کامپوننت‌ها

از زمان دات نت 6 به بعد، امکان محدود کردن بازه‌ی نوع‌های قابل پذیرش T نیز میسر شده‌است:
@typeparam T where T : IMyInterface


روش مشخص کردن صریح نوع پارامترهای جنریک، در حین استفاده‌ی از آن‌ها

عموما نیازی به مشخص کردن نوع پارامترهای جنریک، در حین استفاده‌ی از آن‌ها نیست و کامپایلر بر اساس نوع مقدار ورودی، سعی خواهد کرد این نوع را به صورت خودکار تشخیص دهد؛ اما اگر به هر دلیلی چنین امری ممکن نبود و خطایی را دریافت کردید، می‌توان نوع پارامتر جنریک را به صورت زیر مشخص کرد:
<MyGenericComponent Value="10" T="int"/>
در اینجا نام پارامتر جنریک، ذکر شده و سپس نوعی به آن انتساب داده می‌شود.


روش پردازش پارامترهای جنریک در کامپوننت‌ها

تا اینجا امکان پذیرش نوع‌های جنریک را توسط استفاده کننده میسر کردیم؛ اما قسمت دیگر آن، نحوه‌ی کار با نوع T، درون یک کامپوننت است:
using Microsoft.AspNetCore.Components;

namespace BlazorGenericComponents.Pages;

public partial class MyGenericComponentWithCodeBehind<T>
{
    private string FormattedValue { set; get; }

    [Parameter] public T Value { get; set; }

    [Parameter] public string Format { get; set; }

    protected override void OnParametersSet()
    {
        FormattedValue = ConvertNumberToText();
    }

    private string ConvertNumberToText()
    {
        return Value switch
        {
            short shortValue => shortValue.ToString(Format),
            ushort ushortValue => ushortValue.ToString(Format),
            int intValue => intValue.ToString(Format),
            uint uintValue => uintValue.ToString(Format),
            byte byteValue => byteValue.ToString(Format),
            sbyte sbyteValue => sbyteValue.ToString(Format),
            decimal decimalValue => decimalValue.ToString(Format),
            double doubleValue => doubleValue.ToString(Format),
            float floatValue => floatValue.ToString(Format),
            long longValue => longValue.ToString(Format),
            ulong ulongValue => ulongValue.ToString(Format),
            _ => string.Empty
        };
    }
}
برای مثال فرض کنید کامپوننت جنریک ما قرار است انواع و اقسام اعداد را بپذیرد و سپس بر اساس Format مشخص شده، آن‌ها را نمایش دهد. در اینجا جهت واکنش نشان دادن به تغییرات Value، می‌توان از روال رخ‌داد گردان OnParametersSet استفاده کرد. سپس در متد ConvertNumberToText، بر اساس هر نوع میسر عددی، باید منطق ویژه‌ای را تهیه کرد که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید.

و در آخر نمایش نهایی آن هم در فایل razor. متناظر، به این صورت خواهد بود:
@typeparam T

@FormattedValue
نظرات مطالب
خواندن اطلاعات از سرور و نمایش آن توسط Angular در ASP.NET MVC
در روش بالا اگر چه اطلاعات از سرور دریافت میشود اما حالتی استاتیک دارد چون لیست محصولات در زمان رندر شدن صفحه تولید و در ng-init قرار می‌گیرد . و در نتیجه برای بروزکردن حتما باید صفحه مجددا درخواست بشود (رفرش). این روش برای لیست هایی که تغییراتی ندارند یا به ندرت تغییر می‌کنند مناسب است.
و بله یک راه برای حل این مشکل استفاده از سرویس http می‌تواند باشد .
نظرات مطالب
چه زمان‌هایی یک برنامه‌ی ASP.NET ری استارت می‌شود؟
یک نکته‌ی تکمیلی: چگونه برنامه‌های ASP.NET Core را به صورت دستی ری‌استارت کنیم؟

با توجه به چندسکویی بودن ASP.NET Core، برای نمونه در لینوکس، تغییراتی مانند تغییر در web.config، سبب ری‌استارت برنامه نمی‌شوند. در این حالت برای بارگذاری تغییرات، می‌توان از روش دستی ری‌استارت برنامه استفاده کرد:
[Authorize(Roles = "Admin")] 
public class AdminController : Controller 
{ 
    private readonly IApplicationLifetime _applicationLifetime;
 
    public AdminController(IApplicationLifetime appLifetime) 
    { 
        _applicationLifetime = appLifetime; 
    } 
 
    public IActionResult Shutdown() 
    { 
       _applicationLifetime.StopApplication(); 
       return Content("Done"); 
    } 
}
در اینجا از سرویس توکار IApplicationLifetime و متد StopApplication آن برای recycle/restart برنامه استفاده شده‌است. بدیهی است دسترسی به یک چنین اکشن متدی باید کاملا کنترل شده باشد.
نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
با سلام
برای مدل User شما از Int استفاده کرده اید من با توجه به ساختار کل پروژه که از Guid استفاده می‌کنیم مدل user  رو به صورت زیر تغییر داده ام
    public class User : IdentityUser<Guid, UserLogin, UserRole, UserClaim>
 حال در سرویس  ApplicationUserManager در متد GetCurrentUserId به صورت زیر پیاده سازی شده است
public Guid GetCurrentUserId()
{
    return _identity.GetUserId<Guid>();
}
که بعد از کامپایل خطای زیر داده می‌شود
The type 'System.Guid' cannot be used as type parameter 'T' in the generic type 
or method 'Microsoft.AspNet.Identity.IdentityExtensions.GetUserId<T>(System.Security.Principal.IIdentity)'. 
There is no boxing conversion from 'System.Guid' to 'System.IConvertible'
آیا فقط نوع داده باید int باشد؟
نظرات مطالب
EF Code First #12
این نکته برای برنامه‌های کنسول و سرویس ویندوز است. در برنامه‌های WinForms و WPF با بسته شدن فرم، به صورت خودکار این اشیاء هم تخریب می‌شوند.
public class MyContext : DbContext
    {
        protected override void Dispose(bool disposing)
        {
            Debug.WriteLine("MyContext Dispose() is called.");
            base.Dispose(disposing);
        }
    }
برای بررسی بهتر این موضوع، متد فوق را به کلاس Context برنامه اضافه کنید. یک breakpoint روی آن قرار دهید و بعد فرم را باز کرده، با بانک اطلاعاتی کار کنید و سپس فرم را ببندید.
نظرات مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت ششم - تکمیل مستندات محافظت از API
یک نکته‌ی تکمیلی: نشان دادن لیست API‌ها در swagger فقط برای کاربرانی که لاگین کرده اند

در هنگام توسعه‌ی پروژه شاید برای شما مهم باشد که لیست api‌های شما برای افرادی که لاگین نکرده‌اند، قابل مشاهده نباشد. برای این منظور ابتدا باید سه کتابخانه مربوط به swagger را نصب نمایید:
    <PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />
    <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="4.0.1" />
    <PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="4.5.2" />
سپس یک کلاس را همراه با دو اکستنشن متد برای کانفیگ swagger میسازیم :
    public static class ServiceCollectionExtensions
    {
        public static void AddCustomSwagger(this IServiceCollection services)
        {
            services.AddSwaggerGen(options =>
            {
                options.EnableAnnotations();
                options.DocumentFilter<AuthenticationDocumentFilter>();
                options.SwaggerDoc("v1", new Info { Version = "v1", Title = "Test API" });
            });
        }
        public static void UseSwaggerAndUI(this IApplicationBuilder app)
        {
            app.UseSwagger();
            app.UseSwaggerUI(options =>
            {
                options.DocExpansion(DocExpansion.None);
                options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test API Docs");
            });
        }
    }
در متد AddSwaggerGen از DocumentFilter استفاده کرده‌ایم. با استفاده از Document FIlter‌ها میتوانید خروجی api‌ها را در swagger، توسعه دهید. DocumentFilter که از نوع جنریک است، یک کلاس را به عنوان تایپ قبول میکند که باید از اینترفیس IDocumentFilter ارث بری کرده باشد. اینترفیس IDocumentFilter حاوی یک متد Apply است که دارای دو ورودی از نوع SwaggerDocument  و DocumentFilterContext میباشد. کلاس SwaggerDocument  مستندات api‌ها را در اختیار شما قرار میدهد و میتوانید آنهارا تغییر دهید.
سپس کلاس AuthenticationDocumentFilter را پیاده سازی میکنیم:
  public class AuthenticationDocumentFilter : IDocumentFilter
    {
        private readonly IHttpContextAccessor httpContextAccessor;

        public AuthenticationDocumentFilter(IHttpContextAccessor httpContextAccessor)
        {
            this.httpContextAccessor = httpContextAccessor;
        }

        public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
        {
            if (!httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
            {
                swaggerDoc.Definitions = new Dictionary<string, Schema>();
                swaggerDoc.Paths = new Dictionary<string, PathItem>();
            }
        }
    }
در کلاس AuthenticationDocumentFilter از IHttpContextAccessor برای دسترسی به هویت کاربر استفاده کرده ایم که بعدا باید در متد ConfigureService متد AddHttpContextAccessor را جهت دسترسی به IHttpContextAccessor فراخوانی کنیم. در ادامه اگر کاربر لاگین نکرده باشد، تمامی api‌ها پاک شده و در سمت کاربر هیچ api ای مشاهده نمیشود.
در صورت نیاز میتوان مشخص کرد کدام نوع api هارا نشان ندهد؛ به عنوان مثال Post و Put را نشان ندهد :
        public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
        {
            if (!httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
            {
                foreach (var item in swaggerDoc.Paths)
                {
                    item.Value.Post = null;
                    item.Value.Put = null;
                }
            }
        }
در ادامه برای ثبت سرویس‌ها در کلاس StartUp 
    public void ConfigureServices(IServiceCollection services)
        {
            services.AddHttpContextAccessor();
            services.AddAuthorization();
            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options=>
            {
                options.AccessDeniedPath = "/Login";
                options.Cookie.HttpOnly = true;
                options.LoginPath = "/Login";
                options.LogoutPath = "/Login";
                options.ExpireTimeSpan = TimeSpan.FromDays(15);
                options.SlidingExpiration = true;
                options.Cookie.IsEssential = true;
                options.ReturnUrlParameter = "returnUrl";
            });
            services.AddMvc();
            services.AddCustomSwagger();
        }
و اضافه کردن میان افزار swagger :
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseAuthentication();
            app.UseSwaggerAndUI();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                          name: "default",
                          template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
بازخوردهای پروژه‌ها
self registeration httpmodule
با سلام و عرض خدا قوت و تشکر بابت به اشتراک گذاری 
جهت فارغ شدن از تنظیمات ثبت httpmodule در فایل web.config و درگیر شدن با نسخ iis می‌توان کلاسی به نام ModuleRegistretion  ایجاد کرده و ویژگی assembly را در بالای کدهای خود قبل از شروع تعریف فضای نام قرار داده تا متد RegisterModule  در ابتدای چرخه حیات برنامه اجرا شده و با اجرای دستور   HttpApplication.RegisterModule موجبات ثبت httpmodule فراهم گردد . 
[assembly: PreApplicationStartMethod(typeof(Statistics.Modules.ModuleRegistretion), "RegisterModule")]

namespace Statistics.Modules
{
    public class ModuleRegistretion
    {
        public static void RegisterModule()
        {
            HttpApplication.RegisterModule(typeof(StatModule));
        }
    }
...
}

 همچنین در کدهای ماژول قسمت 
XDocument xdoc = XDocument.Load("http://www.freegeoip.net/xml/" + GetIPAddress());

 بهتر است استثناهای مرتبط مدیریت شوند تا در صورت اختلال و عدم دسترسی به آدرس خارجی ذکر شده مشکلی برای برنامه ایجاد نشود .

نظرات مطالب
Ajax.BeginForm و ارسال فایل به سرور در ASP.NET MVC
با سلام
من یک فرم دارم که در اون کاربر باید دو عکس وارد کنید به صورت زیر
 <div>
        <div style="margin: 2px 4px !important">
            <div>
                عکس 1
                <input type="file" name="imageSrc" id="imageSrc" />
                @*@Html.Kendo().Upload().Name("imageSrc").Multiple(false)*@
                @Html.ValidationMessageFor(model => model.Image)
            </div>
        </div>

    </div>
    <div>
        <div style="margin: 2px 4px !important">
            <div>
                عکس 2
                <input type="file" name="imageSrc2" id="imageSrc2" />
                @*@Html.Kendo().Upload().Name("imageSrc2").Multiple(false)*@
                @Html.ValidationMessageFor(model => model.Image)
            </div>
        </div>

آیا می‌تونم این دو فایل رو با هم آپلود کنم؟ چون در مثال شما  fileElementId: 'Image1' ,  فقط نام یک کنترل را می‌گیرد.
ممنون