در Asp.Net Core به طور پیش فرض کوکی ها هر 30 دقیقه یک بار اعتبارسنجی میشوند. یعنی زمانی که کاربر به سایت لاگین میکند و اطلاعات خود را وارد میکند Claim های فعلی کابر در کوکی ذخیره میشود و میتواند به صفحه های مورد نظر خود دسترسی پیدا کند و تا 30 دقیقه اعتبارسنجی کوکی ها صورت نمیگیرد حتی اگر کاربر رمز عبور خود را تغییر دهد و یا توسط ادمین سایت نقش کاربر تغییر کند. با این حال تا زمانی که کاربر از سایت خارج نشود و مجددا وارد سایت نشود میتواند از کوکی ها استفاده کند و در سایت بماند. اما اگر رمزعبور کاربر به سرقت برود و کاربر بخواهد که رمز عبور خود را تغییر دهد بازهم فرقی نمیکند, چون کوکی در وب سایت کاربری که رمزعبور را به سرقت برده است فعال است و قبل از انکه کاربر رمز عبورش را تغییر دهد وارد سایت شده است. برای حل این مشکل میتوانیم در Event مربوط به کوکی تنظیماتی را قرار دهیم که هربار که درخواستی به سایت ارسال میشود کوکی اعتبار سنجی شود. برای انجام این کار میتوانیم مقدار پراپرتی OnValidatePrincipal به صورت زیر تغییر دهیم.

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
 {
     options.AccessDeniedPath = "/Auth/SignIn";
     options.Cookie.HttpOnly = true;
     options.ExpireTimeSpan = TimeSpan.FromDays(15);
     options.LoginPath = "/Auth/SignIn";
     options.ReturnUrlParameter = "returnUrl";
     options.SlidingExpiration = true;
     options.Cookie.IsEssential = true;// ignore GDPR
     options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
     options.Events = new CookieAuthenticationEvents
     {
         OnValidatePrincipal = ValidateAsync
     };
 });

متد ValidateAync

private static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
    context = context ?? throw new ArgumentNullException(nameof(context));
    var claimsIdentity = context.Principal.Identity as ClaimsIdentity;
    if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any())
    {
        await RejectPrincipal();
        return;
    }
    UserManager<Users> userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<Users>>();
    var user = await userManager.FindByNameAsync(context.Principal.FindFirstValue(ClaimTypes.NameIdentifier));
    if (user == null || user.SecurityStamp != context.Principal.FindFirst(new ClaimsIdentityOptions().SecurityStampClaimType)?.Value)
    {
        await RejectPrincipal();
        return;
    }
    async Task RejectPrincipal()
    {
        context.RejectPrincipal();
        await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    }
}

در این متد ابتدا بررسی شده است که ایا Claim وجود دارد یا نه. اگر وجود نداشته باشد کاربر SignOut میشود و مجددا باید لاگین کند اما اگر Claim در کانتکس مربوطه وجود داشته باشد با استفاده از ClaimTypes.NameIdentifier نام منحصربه فرد کاربر را دریافت میکنیم و کاربر و آن را از دیتابیس میخوانیم و سپس مقدار SecurityStamp کاربر را با SecurityStampClaimType درون کوکی بررسی میکنیم که اگر کاربر null باشد و یا اینکه مقدار SecurityStamp کاربر مخالف مقدار SecurityStampClaimType در کوکی باشد کاربر SignOut میشود و مجددا باید لاگین کند چون اگه کاربر null باشد به معنی ان است که مقدار فیلد NameIdentifier خالی است و هیچ کاربری لاگین نکرده است و اگر مقدار فیلد SecurityStamp با مقدار درون کوکی تفاوت داشته باشد به آن معناست که کاربر پروفایل خود را وایرایش کرده است و مجددا باید لاگین کند. فیلد SecurityStamp هنگامی که کاربر رمز عبور خود را تغییر میدهد و یا هرگونه ویرایشی بر روی کاربر اعمال شود باید بروز رسانی شود. با استفاده از متد UpdateSecurityStampAsync در کلاس UserManager میتوانید این مقدار را بروز رسانی کنید. به صورت زیر 

 await _userManager.UpdateSecurityStampAsync(user);

سپس اگر برنامه را اجرا کنید و در دو مرورگر مختلف وارد سایت شوید ( به عنوان مثال با کروم و incognito لاگین کنیم ) و در تب کروم عادی رمز عبور خود را تغییر دهید ( مقدار SecurityStamp نیز تغییر کند ) و سپس به صفحه incognito بروید و صفحه را رفرش کنید می بینید که نیاز به لاگین ندارید و هنوز کاربر Authenticate است. چون به طور پیشفرض کوکی ها 30 دقیقه یک بار اعتبار سنجی میشوند. برای حل این مشکل باید به صورت زیر عمل کنیم

services.Configure<SecurityStampValidatorOptions>(options =>
{
    options.ValidationInterval = TimeSpan.Zero;
});

با این کار هر بار که درخواستی به سرور ارسال میشود ابتدا کوکی اعتبار سنجی میشود و سپس بقیه کارها انجام میشوند. شاید انجام این کار تاثیر بدی بر روی پرفورمنس سایت داشته باشد اما امنیت سایت تا حدودی افزایش می یابد.

 نمونه پروژه نوشته شده برای این کار همراه با پیاده سازی Identity را میتوانید از گیت هاب دانلود کنید ;)

Powered by Froala Editor