اعتبارسنجی مبتنی بر کوکی‌ها در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: سیزده دقیقه

ASP.NET Core 2.0 به همراه یک AuthenticationMiddleware است که یکی از قابلیت‌های این میان‌افزار، افزودن اطلاعات کاربر و یا همان HttpContext.User به یک کوکی رمزنگاری شده و سپس اعتبارسنجی این کوکی در درخواست‌های بعدی کاربر است. این مورد سبب خواهد شد تا بتوان بدون نیاز به پیاده سازی سیستم کامل ASP.NET Core Identity، یک سیستم اعتبارسنجی سبک، ساده و سفارشی را تدارک دید.



تعریف موجودیت‌های مورد نیاز جهت طراحی یک سیستم اعتبارسنجی

در اینجا کنترل کامل سیستم در اختیار ما است و در این حالت می‌توان طراحی تمام قسمت‌ها را از ابتدا و مطابق میل خود انجام داد. برای مثال سیستم اعتبارسنجی ساده‌ی ما، شامل جدول کاربران و نقش‌های آن‌ها خواهد بود و این دو با هم رابطه‌ی many-to-many دارند. به همین جهت جدول UserRole نیز در اینجا پیش بینی شده‌است.

جدول کاربران

    public class User
    {
        public User()
        {
            UserRoles = new HashSet<UserRole>();
        }

        public int Id { get; set; }

        public string Username { get; set; }

        public string Password { get; set; }

        public string DisplayName { get; set; }

        public bool IsActive { get; set; }

        public DateTimeOffset? LastLoggedIn { get; set; }

        /// <summary>
        /// every time the user changes his Password,
        /// or an admin changes his Roles or stat/IsActive,
        /// create a new `SerialNumber` GUID and store it in the DB.
        /// </summary>
        public string SerialNumber { get; set; }

        public virtual ICollection<UserRole> UserRoles { get; set; }
    }
در اینجا SerialNumber فیلدی است که با هر بار ویرایش اطلاعات کاربران باید از طرف برنامه به روز رسانی شود. از آن جهت غیرمعتبر سازی کوکی کاربر استفاده خواهیم کرد. برای مثال اگر خاصیت فعال بودن او تغییر کرد و یا نقش‌های او را تغییر دادیم، کاربر در همان لحظه باید logout شود. به همین جهت چنین فیلدی در اینجا در نظر گرفته شده‌است تا با بررسی آن بتوان وضعیت معتبر بودن کوکی او را تشخیص داد.

جدول نقش‌های کاربران

    public class Role
    {
        public Role()
        {
            UserRoles = new HashSet<UserRole>();
        }

        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<UserRole> UserRoles { get; set; }
    }
البته این سیستم ساده دارای یک سری نقش ثابت و مشخص است.
    public static class CustomRoles
    {
        public const string Admin = nameof(Admin);
        public const string User = nameof(User);
    }
که این نقش‌ها را در ابتدای کار برنامه به بانک اطلاعات اضافه خواهیم کرد.


جدول ارتباط نقش‌ها با کاربران و برعکس

    public class UserRole
    {
        public int UserId { get; set; }
        public int RoleId { get; set; }

        public virtual User User { get; set; }
        public virtual Role Role { get; set; }
    }
وجود این جدول در EF Core جهت تعریف یک رابطه‌ی many-to-many ضروری است.


تعریف Context برنامه و فعالسازی Migrations در EF Core 2.0

DbContext برنامه را به صورت ذیل در یک اسمبلی دیگر اضافه خواهیم کرد:
    public interface IUnitOfWork : IDisposable
    {
        DbSet<TEntity> Set<TEntity>() where TEntity : class;

        int SaveChanges(bool acceptAllChangesOnSuccess);
        int SaveChanges();
        Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken());
        Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken());
    }

    public class ApplicationDbContext : DbContext, IUnitOfWork
    {
        public ApplicationDbContext(DbContextOptions options) : base(options)
        { }

        public virtual DbSet<User> Users { set; get; }
        public virtual DbSet<Role> Roles { set; get; }
        public virtual DbSet<UserRole> UserRoles { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            // it should be placed here, otherwise it will rewrite the following settings!
            base.OnModelCreating(builder);

            // Custom application mappings
            builder.Entity<User>(entity =>
            {
                entity.Property(e => e.Username).HasMaxLength(450).IsRequired();
                entity.HasIndex(e => e.Username).IsUnique();
                entity.Property(e => e.Password).IsRequired();
                entity.Property(e => e.SerialNumber).HasMaxLength(450);
            });

            builder.Entity<Role>(entity =>
            {
                entity.Property(e => e.Name).HasMaxLength(450).IsRequired();
                entity.HasIndex(e => e.Name).IsUnique();
            });

            builder.Entity<UserRole>(entity =>
            {
                entity.HasKey(e => new { e.UserId, e.RoleId });
                entity.HasIndex(e => e.UserId);
                entity.HasIndex(e => e.RoleId);
                entity.Property(e => e.UserId);
                entity.Property(e => e.RoleId);
                entity.HasOne(d => d.Role).WithMany(p => p.UserRoles).HasForeignKey(d => d.RoleId);
                entity.HasOne(d => d.User).WithMany(p => p.UserRoles).HasForeignKey(d => d.UserId);
            });
        }
    }
در اینجا موجودیت‌های برنامه به صورت DbSet در معرض دید EF Core 2.0 قرار گرفته‌اند. همچنین رابطه‌ی Many-to-Many بین نقش‌ها و کاربران نیز تنظیم شده‌است.
سازنده‌ی کلاس به همراه پارامتر DbContextOptions است تا بتوان آن‌را در آغاز برنامه تغییر داد.


فعالسازی مهاجرت‌ها در EF Core 2.0

EF Core 2.0 برخلاف نگارش‌های قبلی آن به دنبال کلاسی مشتق شده‌ی از IDesignTimeDbContextFactory می‌گردد تا بتواند نحوه‌ی وهله سازی ApplicationDbContext را دریافت کند. در اینجا چون DbContext تعریف شده دارای یک سازنده‌ی با پارامتر است، EF Core 2.0 نمی‌داند که چگونه باید آن‌را در حین ساخت مهاجرت‌ها و اعمال آن‌ها، وهله سازی کند. کار کلاس ApplicationDbContextFactory ذیل دقیقا مشخص سازی همین مساله است:
    /// <summary>
    /// Only used by EF Tooling
    /// </summary>
    public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
    {
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var basePath = Directory.GetCurrentDirectory();
            Console.WriteLine($"Using `{basePath}` as the BasePath");
            var configuration = new ConfigurationBuilder()
                                    .SetBasePath(basePath)
                                    .AddJsonFile("appsettings.json")
                                    .Build();
            var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
            var connectionString = configuration.GetConnectionString("DefaultConnection");
            builder.UseSqlServer(connectionString);
            return new ApplicationDbContext(builder.Options);
        }
    }
کاری که در اینجا انجام شده، خواندن DefaultConnection از قسمت ConnectionStrings فایل appsettings.json است:
{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=(LocalDB)\\MSSQLLocalDB;Initial Catalog=ASPNETCore2CookieAuthenticationDB;Integrated Security=True;MultipleActiveResultSets=True;"
  },
  "LoginCookieExpirationDays": 30
}
و سپس استفاده‌ی از آن جهت تنظیم رشته‌ی اتصالی متد UseSqlServer و در آخر وهله سازی ApplicationDbContext.
کار یافتن این کلاس در حین تدارک و اعمال مهاجرت‌ها توسط EF Core 2.0 خودکار بوده و باید محل قرارگیری آن دقیقا در اسمبلی باشد که DbContext برنامه در آن تعریف شده‌است.


تدارک لایه سرویس‌های برنامه

پس از مشخص شدن ساختار موجودیت‌ها و همچنین Context برنامه، اکنون می‌توان لایه سرویس برنامه را به صورت ذیل تکمیل کرد:

سرویس کاربران
    public interface IUsersService
    {
        Task<string> GetSerialNumberAsync(int userId);
        Task<User> FindUserAsync(string username, string password);
        Task<User> FindUserAsync(int userId);
        Task UpdateUserLastActivityDateAsync(int userId);
    }
کار این سرویس ابتدایی کاربران، یافتن یک یک کاربر بر اساس Id او و یا کلمه‌ی عبور و نام کاربری او است. از این امکانات در حین لاگین و یا اعتبارسنجی کوکی کاربر استفاده خواهیم کرد.
پیاده سازی کامل این سرویس را در اینجا می‌توانید مشاهده کنید.

سرویس نقش‌های کاربران
    public interface IRolesService
    {
        Task<List<Role>> FindUserRolesAsync(int userId);
        Task<bool> IsUserInRole(int userId, string roleName);
        Task<List<User>> FindUsersInRoleAsync(string roleName);
    }
از این سرویس برای یافتن نقش‌های کاربر لاگین شده‌ی به سیستم و افزودن آن‌ها به کوکی رمزنگاری شده‌ی اعتبارسنجی او استفاده خواهیم کرد.
پیاده سازی کامل این سرویس را در اینجا می‌توانید مشاهده کنید.

سرویس آغاز بانک اطلاعاتی

    public interface IDbInitializerService
    {
        void Initialize();
        void SeedData();
    }
از این سرویس در آغاز کار برنامه برای اعمال خودکار مهاجرت‌های تولیدی و همچنین ثبت نقش‌های آغازین سیستم به همراه افزودن کاربر Admin استفاده خواهیم کرد.
پیاده سازی کامل این سرویس را در اینجا می‌توانید مشاهده کنید.

سرویس اعتبارسنجی کوکی‌های کاربران

یکی از قابلیت‌های میان‌افزار اعتبارسنجی ASP.NET Core 2.0، رخ‌دادی است که در آن اطلاعات کوکی دریافتی از کاربر، رمزگشایی شده و در اختیار برنامه جهت تعیین اعتبار قرار می‌گیرد:
    public interface ICookieValidatorService
    {
        Task ValidateAsync(CookieValidatePrincipalContext context);
    }

    public class CookieValidatorService : ICookieValidatorService
    {
        private readonly IUsersService _usersService;
        public CookieValidatorService(IUsersService usersService)
        {
            _usersService = usersService;
            _usersService.CheckArgumentIsNull(nameof(usersService));
        }

        public async Task ValidateAsync(CookieValidatePrincipalContext context)
        {
            var userPrincipal = context.Principal;

            var claimsIdentity = context.Principal.Identity as ClaimsIdentity;
            if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any())
            {
                // this is not our issued cookie
                await handleUnauthorizedRequest(context);
                return;
            }

            var serialNumberClaim = claimsIdentity.FindFirst(ClaimTypes.SerialNumber);
            if (serialNumberClaim == null)
            {
                // this is not our issued cookie
                await handleUnauthorizedRequest(context);
                return;
            }

            var userIdString = claimsIdentity.FindFirst(ClaimTypes.UserData).Value;
            if (!int.TryParse(userIdString, out int userId))
            {
                // this is not our issued cookie
                await handleUnauthorizedRequest(context);
                return;
            }

            var user = await _usersService.FindUserAsync(userId).ConfigureAwait(false);
            if (user == null || user.SerialNumber != serialNumberClaim.Value || !user.IsActive)
            {
                // user has changed his/her password/roles/stat/IsActive
                await handleUnauthorizedRequest(context);
            }

            await _usersService.UpdateUserLastActivityDateAsync(userId).ConfigureAwait(false);
        }

        private Task handleUnauthorizedRequest(CookieValidatePrincipalContext context)
        {
            context.RejectPrincipal();
            return context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        }
    }
کار این سرویس، تعیین اعتبار موارد ذیل است:
- آیا کوکی دریافت شده دارای اطلاعات HttpContext.User است؟
- آیا این کوکی به همراه اطلاعات فیلد SerialNumber است؟
- آیا این کوکی به همراه Id کاربر است؟
- آیا کاربری که بر اساس این Id یافت می‌شود غیرفعال شده‌است؟
- آیا کاربری که بر اساس این Id یافت می‌شود دارای SerialNumber یکسانی با نمونه‌ی موجود در بانک اطلاعاتی است؟

اگر خیر، این اعتبارسنجی رد شده و بلافاصله کوکی کاربر نیز معدوم خواهد شد.


تنظیمات ابتدایی میان‌افزار اعتبارسنجی کاربران در ASP.NET Core 2.0

تنظیمات کامل ابتدایی میان‌افزار اعتبارسنجی کاربران در ASP.NET Core 2.0 را در فایل Startup.cs می‌توانید مشاهده کنید.

ابتدا سرویس‌های برنامه معرفی شده‌اند:
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IUnitOfWork, ApplicationDbContext>();
            services.AddScoped<IUsersService, UsersService>();
            services.AddScoped<IRolesService, RolesService>();
            services.AddScoped<ISecurityService, SecurityService>();
            services.AddScoped<ICookieValidatorService, CookieValidatorService>();
            services.AddScoped<IDbInitializerService, DbInitializerService>();

سپس تنظیمات مرتبط با ترزیق وابستگی‌های ApplicationDbContext برنامه انجام شده‌است. در اینجا رشته‌ی اتصالی، از فایل appsettings.json خوانده شده و سپس در اختیار متد UseSqlServer قرار می‌گیرد:
            services.AddDbContext<ApplicationDbContext>(options =>
            {
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection"),
                    serverDbContextOptionsBuilder =>
                        {
                            var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                            serverDbContextOptionsBuilder.CommandTimeout(minutes);
                            serverDbContextOptionsBuilder.EnableRetryOnFailure();
                        });
            });

در ادامه تعدادی Policy مبتنی بر نقش‌های ثابت سیستم را تعریف کرده‌ایم. این کار اختیاری است اما روش توصیه شده‌ی در ASP.NET Core، کار با Policyها است تا کار مستقیم با نقش‌ها. Policy‌ها انعطاف پذیری بیشتری را نسبت به نقش‌ها ارائه می‌دهند و در اینجا به سادگی می‌توان چندین نقش و یا حتی Claim را با هم ترکیب کرد و به صورت یک Policy ارائه داد:
            // Only needed for custom roles.
            services.AddAuthorization(options =>
                    {
                        options.AddPolicy(CustomRoles.Admin, policy => policy.RequireRole(CustomRoles.Admin));
                        options.AddPolicy(CustomRoles.User, policy => policy.RequireRole(CustomRoles.User));
                    });

قسمت اصلی تنظیمات میان افزار اعتبارسنجی مبتنی بر کوکی‌ها در اینجا قید شده‌است:
            // Needed for cookie auth.
            services
                .AddAuthentication(options =>
                {
                    options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                })
                .AddCookie(options =>
                {
                    options.SlidingExpiration = false;
                    options.LoginPath = "/api/account/login";
                    options.LogoutPath = "/api/account/logout";
                    //options.AccessDeniedPath = new PathString("/Home/Forbidden/");
                    options.Cookie.Name = ".my.app1.cookie";
                    options.Cookie.HttpOnly = true;
                    options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
                    options.Cookie.SameSite = SameSiteMode.Lax;
                    options.Events = new CookieAuthenticationEvents
                    {
                        OnValidatePrincipal = context =>
                        {
                            var cookieValidatorService = context.HttpContext.RequestServices.GetRequiredService<ICookieValidatorService>();
                            return cookieValidatorService.ValidateAsync(context);
                        }
                    };
                });
ابتدا مشخص شده‌است که روش مدنظر ما، اعتبارسنجی مبتنی بر کوکی‌ها است و سپس تنظیمات مرتبط با کوکی رمزنگاری شده‌ی برنامه مشخص شده‌اند. تنها قسمت مهم آن CookieAuthenticationEvents است که نحوه‌ی پیاده سازی آن‌را با معرفی سرویس ICookieValidatorService پیشتر بررسی کردیم. این قسمت جائی است که پس از هر درخواست به سرور اجرا شده و کوکی رمزگشایی شده، در اختیار برنامه جهت اعتبارسنجی قرار می‌گیرد.

کار نهایی تنظیمات میان افزار اعتبارسنجی در متد Configure با فراخوانی UseAuthentication صورت می‌گیرد. اینجا است که میان افزار، به برنامه معرفی خواهد شد:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   app.UseAuthentication();

همچنین پس از آن، کار اجرای سرویس آغاز بانک اطلاعاتی نیز انجام شده‌است تا نقش‌ها و کاربر Admin را به سیستم اضافه کند:
            var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
            using (var scope = scopeFactory.CreateScope())
            {
                var dbInitializer = scope.ServiceProvider.GetService<IDbInitializerService>();
                dbInitializer.Initialize();
                dbInitializer.SeedData();
            }


پیاده سازی ورود و خروج به سیستم

پس از این مقدمات به مرحله‌ی آخر پیاده سازی این سیستم اعتبارسنجی می‌رسیم.

پیاده سازی Login
در اینجا از سرویس کاربران استفاده شده و بر اساس نام کاربری و کلمه‌ی عبور ارسالی به سمت سرور، این کاربر یافت خواهد شد.
در صورت وجود این کاربر، مرحله‌ی نهایی کار، فراخوانی متد الحاقی HttpContext.SignInAsync است:
        [AllowAnonymous]
        [HttpPost("[action]")]
        public async Task<IActionResult> Login([FromBody]  User loginUser)
        {
            if (loginUser == null)
            {
                return BadRequest("user is not set.");
            }

            var user = await _usersService.FindUserAsync(loginUser.Username, loginUser.Password).ConfigureAwait(false);
            if (user == null || !user.IsActive)
            {
                await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                return Unauthorized();
            }

            var loginCookieExpirationDays = _configuration.GetValue<int>("LoginCookieExpirationDays", defaultValue: 30);
            var cookieClaims = await createCookieClaimsAsync(user).ConfigureAwait(false);
            await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                cookieClaims,
                new AuthenticationProperties
                {
                    IsPersistent = true, // "Remember Me"
                    IssuedUtc = DateTimeOffset.UtcNow,
                    ExpiresUtc = DateTimeOffset.UtcNow.AddDays(loginCookieExpirationDays)
                });

            await _usersService.UpdateUserLastActivityDateAsync(user.Id).ConfigureAwait(false);

            return Ok();
        }
مهم‌ترین کار متد HttpContext.SignInAsync علاوه بر تنظیم طول عمر کوکی کاربر، قسمت createCookieClaimsAsync است که به صورت ذیل پیاده سازی شده‌است:
        private async Task<ClaimsPrincipal> createCookieClaimsAsync(User user)
        {
            var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
            identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
            identity.AddClaim(new Claim("DisplayName", user.DisplayName));

            // to invalidate the cookie
            identity.AddClaim(new Claim(ClaimTypes.SerialNumber, user.SerialNumber));

            // custom data
            identity.AddClaim(new Claim(ClaimTypes.UserData, user.Id.ToString()));

            // add roles
            var roles = await _rolesService.FindUserRolesAsync(user.Id).ConfigureAwait(false);
            foreach (var role in roles)
            {
                identity.AddClaim(new Claim(ClaimTypes.Role, role.Name));
            }

            return new ClaimsPrincipal(identity);
        }
در اینجا است که اطلاعات اضافی کاربر مانند Id او یا نقش‌های او به کوکی رمزنگاری شده‌ی تولیدی توسط HttpContext.SignInAsync اضافه شده و در دفعات بعدی و درخواست‌های بعدی او، به صورت خودکار توسط مرورگر به سمت سرور ارسال خواهند شد. این کوکی است که امکان کار با MyProtectedApiController و یا MyProtectedAdminApiController را فراهم می‌کند. اگر شخص لاگین کرده باشد، بلافاصله قابلیت دسترسی به امکانات محدود شده‌ی توسط فیلتر Authorize را خواهد یافت. همچنین در این مثال چون کاربر Admin لاگین می‌شود، امکان دسترسی به Policy مرتبطی را نیز خواهد یافت:
[Route("api/[controller]")]
[Authorize(Policy = CustomRoles.Admin)]
public class MyProtectedAdminApiController : Controller

پیاده سازی Logout

متد الحاقی HttpContext.SignOutAsync کار Logout کاربر را تکمیل می‌کند.
        [AllowAnonymous]
        [HttpGet("[action]"), HttpPost("[action]")]
        public async Task<bool> Logout()
        {
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            return true;
        }


آزمایش نهایی برنامه

در فایل index.html ، نمونه‌ای از متدهای لاگین، خروج و فراخوانی اکشن متدهای محافظت شده را مشاهده می‌کنید. این روش برای برنامه‌های تک صفحه‌ای وب یا SPA نیز می‌تواند مفید باشد و به همین نحو کار می‌کنند.



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
  • #
    ‫۶ سال و ۵ ماه قبل، چهارشنبه ۲۹ فروردین ۱۳۹۷، ساعت ۰۱:۲۰
    با تشکر از شما؛ برای سفارشی کردن نقش‌ها برای دسترسی به اکشن‌ها و کنترل‌ها در این روش راه خاصی وجود دارد
    • #
      ‫۶ سال و ۵ ماه قبل، چهارشنبه ۲۹ فروردین ۱۳۹۷، ساعت ۰۲:۱۱
      مبحث AddPolicy که در مطلب « سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا » توضیح داده شده، وابستگی به ASP.NET Core Identity ندارد و مستقل از آن، مرتبط به جزء Security مجموعه ASP.NET Core است که قسمتی از آن در مطلب جاری استفاده شده‌است. بنابراین نکات آن‌را به همراه یک جدول اضافه‌تر RoleClaims، اینجا هم اضافه کنید، کار می‌کند و هیچ تفاوتی ندارد.
  • #
    ‫۶ سال و ۵ ماه قبل، چهارشنبه ۲۹ فروردین ۱۳۹۷، ساعت ۱۷:۱۴
    با سلام  من تمام مراحل شما رو انجام دادم وقتی از کد زیر استفاده میکنم  با خطای فوق روبه رو می‌شوم.
      Authorize(Policy = CustomRoles.Admin)]


     
    • #
      ‫۶ سال و ۵ ماه قبل، چهارشنبه ۲۹ فروردین ۱۳۹۷، ساعت ۱۷:۲۴
      مخزن کد معرفی شده را برای یافتن CustomRoles.Admin جستجو کنید. قسمت‌های DbInitializerService و options.AddPolicy یافت شده‌، اصل تعاریف مرتبط با آن هستند.
      • #
        ‫۶ سال و ۵ ماه قبل، جمعه ۳۱ فروردین ۱۳۹۷، ساعت ۰۲:۰۵
        ممنون. با بررسی‌های که کردم متوجه شدم مشکل از این خط کد هست، ولی نتونستم رفعش کنم. آیا جایی را از قلم انداختم برای پیاده سازی؟ با تشکر
          await HttpContext.SignInAsync(
                        CookieAuthenticationDefaults.AuthenticationScheme,
                         cookieClaims,
                        new AuthenticationProperties
                        {
                            IsPersistent =false, // "Remember Me"
                            IssuedUtc = DateTimeOffset.UtcNow,
                            ExpiresUtc = DateTimeOffset.UtcNow.AddDays(loginCookieExpirationDays)
                        });

        InvalidOperationException: No IAuthenticationSignInHandler is configured to handle sign in for the scheme: Cookies

        • #
          ‫۶ سال و ۵ ماه قبل، جمعه ۳۱ فروردین ۱۳۹۷، ساعت ۰۲:۴۷
          محتویات فایل startup خودتان را سطر به سطر با فایل startup مطلب جاری انطباق دهید؛ خصوصا قسمت تعاریف Scheme پیش‌فرض آن.
          • #
            ‫۶ سال و ۵ ماه قبل، جمعه ۳۱ فروردین ۱۳۹۷، ساعت ۰۳:۵۳
            تشکر میکنم؛ مشکل حل شد. من از StructureMap استفاده کرده بودم. احیانا مشکل از اونجا بود. من از این لینک این و یا این برای بازنویسی استفاده می‌کنم که وقت اجرا خطا میداد و بانک Initialize نمیشد ونال برگشت میداد.
             public IServiceProvider ConfigureServices(IServiceCollection services)
                    {
                       // services.AddScoped<IUnitOfWork, ApplicationDbContext>();
                        //services.AddScoped<IUsersService, UsersService>();
                        //services.AddScoped<IRolesService, RolesService>();
                        //services.AddScoped<ISecurityService, SecurityService>();
            
                        //services.AddScoped<ICookieValidatorService, CookieValidatorService>();
                        //services.AddScoped<IDbInitializerService, DbInitializerService>();
                        var container = new Container();
                        container.Configure(config =>
             {
                            config.AddRegistry(new MyStructuremapRegistry());
                            config.Populate(services);
            
                        });
            
                       services.AddDbContext<ApplicationDbContext>(options =>
                        {
                          options.UseSqlServer(
                          Configuration.GetConnectionString("DefaultConnection"),
                            serverDbContextOptionsBuilder =>  {
            
                                    var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                                    serverDbContextOptionsBuilder.CommandTimeout(minutes);
                                    serverDbContextOptionsBuilder.EnableRetryOnFailure();
            
                                });
                        });
            
                        // Only needed for custom roles.
                        services.AddAuthorization(options =>
                       {
                            options.AddPolicy(CustomRoles.Admin, policy => policy.RequireRole(CustomRoles.Admin));
                            options.AddPolicy(CustomRoles.User, policy => policy.RequireRole(CustomRoles.User));
                        });
                        // Needed for cookie auth.
                       services
                            .AddAuthentication(options =>
                            {
                               options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                                options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                               options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                            })
                            .AddCookie(options =>
                            {
                                options.SlidingExpiration = false;
                                options.LoginPath = "/api/account/login";
                                options.LogoutPath = "/api/account/logout";
                                //options.AccessDeniedPath = new PathString("/Home/Forbidden/");
                                options.Cookie.Name = ".my.app1.cookie";
                                options.Cookie.HttpOnly = true;
                                options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
                                options.Cookie.SameSite = SameSiteMode.Lax;
                                options.Events = new CookieAuthenticationEvents
                                {
                                    OnValidatePrincipal = context =>
                                    {
                                    var cookieValidatorService = context.HttpContext.RequestServices.GetRequiredService<ICookieValidatorService>();
            
                                    return cookieValidatorService.ValidateAsync(context);
                                    }
            
                                };
            
                            });
                        services.AddCors(options =>
                        {
                           options.AddPolicy("CorsPolicy",
                                builder => builder
                                    .AllowAnyOrigin()
                                    .AllowAnyMethod()
                                    .AllowAnyHeader()
                                    .AllowCredentials());
            
                        });
                        services.AddMvc();
                        return container.GetInstance<IServiceProvider>();
                    }
            • #
              ‫۶ سال و ۵ ماه قبل، جمعه ۳۱ فروردین ۱۳۹۷، ساعت ۰۴:۰۴
              - استراکچرمپ یک پروژه‌ی تمام شده‌است
              + پس از مدتی کار با NET Core. به این نتیجه خواهید رسید که هیچ نیازی به استفاده از استراکچرمپ و یا IoC Containers ثالث در اینجا نیست و امکانات توکار خود آن برای مدیریت اغلب پروژه‌ها کافی است. در کل وابستگی ثالث کمتر = نگهداری ساده‌تر پروژه در دراز مدت و همچنین سربار اجرایی کمتر.
              - اگر سورس پروژه‌های منتشر شده‌ی مبتنی بر NET Core. را در طی این چندسال بررسی کنید، در عمل کسی از IoC Continers ثالث در اینجا استفاده نمی‌کند و اغلب پروژه‌ها (حتی پروژه‌های بزرگ) از همان امکانات توکار تزریق وابستگی‌های آن استفاده می‌کنند.
              • #
                ‫۶ سال و ۵ ماه قبل، جمعه ۳۱ فروردین ۱۳۹۷، ساعت ۱۲:۳۳
                با تشکر
                 از این مقاله ارتقاء به ASP.NET Core 1.0 - قسمت 6 - سرویس‌ها و تزریق وابستگی‌ها که مربوط به  یک سال پیش می‌باشد کمک گرفتم برای جایگزینی استراکچرمپ. که مزایای استراکچرمپ نسبت به سیستم توکارASP.NET Core پرداخت شده است.
                • #
                  ‫۶ سال و ۵ ماه قبل، جمعه ۳۱ فروردین ۱۳۹۷، ساعت ۱۲:۴۴
                  اگر از این مزایا در پروژه‌ی خود استفاده‌ای نمی‌کنید، آن‌را اضافه نکنید.
  • #
    ‫۶ سال و ۲ ماه قبل، شنبه ۲ تیر ۱۳۹۷، ساعت ۱۶:۴۲
    با سلام؛ در این قسمت کد
     public CookieValidatorService(IUsersService usersService)
            {
                _usersService = usersService;
                _usersService.CheckArgumentIsNull(nameof(usersService));
            }
    آیا نوشتن کد زیر ضرروی است؟ چون همیشه ما پارامتر ارسال میکنیم. دلیل خاصی دارد استفاده شود؟ با تشکر
      _usersService.CheckArgumentIsNull(nameof(usersService));
    • #
      ‫۶ سال و ۲ ماه قبل، شنبه ۲ تیر ۱۳۹۷، ساعت ۱۶:۴۹
      fail fast در صورت عدم تنظیم صحیح تزریق وابستگی‌ها.
  • #
    ‫۶ سال و ۲ ماه قبل، چهارشنبه ۶ تیر ۱۳۹۷، ساعت ۰۶:۳۰
    آیا می‌توان بدون استفاده از JWT در برنامه‌های سمت کلاینت هم کار اعتبارسنجی را انجام داد؟
    بله. یک نمونه مثال از یک برنامه‌ی Angular که از روش مبتنی بر کوکی ASP.NET Core برای اعتبارسنجی استفاده می‌کند (بجای استفاده‌ی از JWT). 
  • #
    ‫۵ سال و ۵ ماه قبل، دوشنبه ۲۷ اسفند ۱۳۹۷، ساعت ۰۲:۳۷
    با سلام، من مشکل Signout شدن زود هنگام کاربر را دارم، اگر بخواهیم این بخش را برای یک ساعت فعال کنیم دقیقا در چه بخشی باید صورت بگیرید چون به نظر از دو بخش قابل تنظیم است یکی در Services > AddCookie > Options و دیگری در AuthenticationProperties و آیا
    ساعت باید بصورت UTC وارد شود یا محلی؟ در پروژه نمونه 30 روز گذاشته شده که زمان زیادی است برای ماندن کاربر در سیستم.
    • #
      ‫۵ سال و ۵ ماه قبل، دوشنبه ۲۷ اسفند ۱۳۹۷، ساعت ۱۰:۵۲
      - یکی برای هدرهای کوکی است و یکی برای تنظیم مشخصات توکن.
      The TimeSpan after which the authentication ticket stored inside the cookie expires. 
      ExpireTimeSpan is added to the current time to create the expiration time for the ticket. 
      The ExpiredTimeSpan value always goes into the encrypted AuthTicket verified by the server. 
      It may also go into the Set-Cookie header, but only if IsPersistent is set. 
      To set IsPersistent to true, configure the AuthenticationProperties passed to SignInAsync. 
      The default value of ExpireTimeSpan is 14 days.
  • #
    ‫۴ سال و ۹ ماه قبل، سه‌شنبه ۵ آذر ۱۳۹۸، ساعت ۱۸:۱۱
    با سلام؛ من از روش بالا برای بحث اعتبارسنجی  و دسترسی به کنترل و اکشن استفاده کردم. اگر کوکی منقضی شودبه صفحه لاگین هدایت می‌شود. تغییری در برنامه دادم اگر کوکی منقضی شد در همان صفحه جاری  پنجره‌ای به صورت مدال باز شود لاگین را انجام دهد. فرم مدال بصورت ajax سمت سرور می‌رود هنگام لاگین مدال. برنامه رو دیباگ کردم کوکی را درست می‌سازد و پر می‌کند. ولی هنگامی که روی اکشنی  کلیک میکنم مقدار کوکی ست نشده خالی هست. عدم دسترسی میدهد. به عبارتی user.Identity.IsAuthenticated رو false بر میگردونه. آیا مشکل درخواست‌های اژاکسی می‌باشد؟ با تشکر
    • #
      ‫۴ سال و ۸ ماه قبل، یکشنبه ۱ دی ۱۳۹۸، ساعت ۱۳:۵۳
      با سلام؛ من متوجه شدم درخواست‌هایی که بصورت اژاکسی صدا زده می‌شوند، نمی‌توانند کوکی رو ست کنند_(modify cookie from ajax call)، برای همین عدم دسترسی می‌دهد (کوکی هدر روی درخواست‌های http نمی‌تواند تغییر بدهد). آیا راهی وجود دارد با درخواست ajax کوکی رو ست کنیم؟
      • #
        ‫۴ سال و ۸ ماه قبل، یکشنبه ۱ دی ۱۳۹۸، ساعت ۱۴:۰۳
        اینطور نیست. مثال مطلب جاری با jQuery Ajax پیاده سازی شده و بدون مشکل کار می‌کند. عدم ارسال کوکی‌ها مرتبط است به درخواست‌هایی که شرایط CORS را دارند: «برای رفع این مشکل نیاز است خاصیت withCredentials را به true تنظیم کنید ، در انتهای آن مطلب»
        • #
          ‫۴ سال و ۸ ماه قبل، سه‌شنبه ۱۷ دی ۱۳۹۸، ساعت ۱۷:۴۹
          ممنون. درست است. کوکی ارسال می‌شود، ولی ValidateAntiForgeryToken درست مقدار دهی نمی‌شود و هنگام ارسال یک درخواست به سمت سرور خطای Bad Request  400 می‌دهد. وقتی  [IgnoreAntiforgeryToken] را روی آن اکشن فعال می‌کنم، مشکل حل می‌شود. ولی در حالت ValidateAntiForgeryToken  درست کار نمی‌کند:
            beforeSend: function (request) {
                       request.withCredentials = true;
                      sendRequestVerificationToken(request);
                  },
          
          function sendRequestVerificationToken(request) {
              request.setRequestHeader("RequestVerificationToken", $("[name='__RequestVerificationToken']").val());
          }
          • #
            ‫۴ سال و ۸ ماه قبل، سه‌شنبه ۱۷ دی ۱۳۹۸، ساعت ۱۸:۰۴
            - ابتدا باید هدر مورد انتظار را مشخص کنید:
            public void ConfigureServices(IServiceCollection services)
            {
               services.AddMvc();
               services.AddAntiforgery(options => options.HeaderName = "__RequestVerificationToken"); 
            }
            - بعد متدی را برای تولید توکن در فایل razor درج کنید:
            @inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
            
            @functions{
                public string GetAntiXsrfRequestToken()
                {
                    return Xsrf.GetAndStoreTokens(Context).RequestToken;
                }
            }
            - سپس اگر از jQuery استفاده می‌کنید، به صورت زیر این هدر با هر درخواستی به سمت سرور ارسال می‌شود:
            <script type="text/javascript">
              $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
                  jqXHR.setRequestHeader("__RequestVerificationToken", '@GetAntiXsrfRequestToken()');
              });
            </script>
  • #
    ‫۴ سال قبل، شنبه ۸ شهریور ۱۳۹۹، ساعت ۰۱:۰۵
    سلام؛ در برخی قسمت‌ها از کد، از ConfigureAwait(false) استفاده شده است، آیا در ورژن‌های قبلی دات نت کور این کد نیاز بوده یا در دانت کور 3.x هم نیازه؟
    • #
      ‫۴ سال قبل، شنبه ۸ شهریور ۱۳۹۹، ساعت ۰۱:۰۹
      اضافی هست. حذفش کنید.
  • #
    ‫۳ سال و ۶ ماه قبل، شنبه ۲۵ بهمن ۱۳۹۹، ساعت ۱۷:۰۳
    سلام
    ممنون میشم راهنمایی کنید، چطوری میشه بعد از معتبر نبودن ValidateAsync  به یک آدرس redirect انجام داد؟
    • #
      ‫۳ سال و ۶ ماه قبل، شنبه ۲۵ بهمن ۱۳۹۹، ساعت ۱۹:۵۸
      آن قسمت سمت سرور به همین صورت باید باقی بماند. زمانیکه درخواستی برگشت می‌خورد، باید در سمت کلاینت xhr.status را بررسی و عکس العمل نشان دهید؛ شبیه به نکات مطلب « هدایت خودکار کاربر به صفحه لاگین در حین اعمال Ajax ایی » و window.location آن.
      • #
        ‫۳ سال و ۶ ماه قبل، دوشنبه ۲۷ بهمن ۱۳۹۹، ساعت ۱۲:۵۵
        ممنون از پاسختون ، فکر کنم این هم جواب بده:
        app.UseCookieAuthentication(new CookieAuthenticationOptions {
                LoginPath = new PathString("/Login/"),
                AuthenticationType = "My-Magical-Authentication",
                // etc...
                },
        });
        • #
          ‫۳ سال و ۶ ماه قبل، دوشنبه ۲۷ بهمن ۱۳۹۹، ساعت ۱۴:۲۴
          این مسیرها تنظیم شده‌اند و در حالت استفاده از Ajax، به صورت هدر Response، به سمت کلاینت ارسال می‌شوند. حالت Redirect ای که در برنامه‌های MVC مشاهده می‌کنید، مرتبط است با استفاده از Razor و فراخوانی صریح SignOutAsync در سمت سرور که از این تنظیمات استفاده می‌کند. بنابراین بستگی دارد که از چه روشی استفاده می‌کنید؟ Razor یا Ajax.
  • #
    ‫۲ سال و ۶ ماه قبل، دوشنبه ۲ اسفند ۱۴۰۰، ساعت ۱۵:۳۷
    باسلام و عرض ارادت
    من به مشکل زیر برخورد کردم

    و به شکل زیر پیاده سازی CheckArgumentIsNull را در ApplicationDbContext انجام دادم:

    public interface IUnitOfWork : IDisposable
        {
            DbSet<TEntity> Set<TEntity>() where TEntity : class;
    
            int SaveChanges(bool acceptAllChangesOnSuccess);
            int SaveChanges();
            Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken());
            Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken());
            void CheckArgumentIsNull(string v);
    void CheckArgumentIsNull(string arg)
            {
                if (arg == null)
                    throw new ArgumentNullException(nameof(arg));
            }

    آیا کاری که کردم درست است؟

    • #
      ‫۲ سال و ۶ ماه قبل، دوشنبه ۲ اسفند ۱۴۰۰، ساعت ۱۵:۵۴
      خودم متوجه اشتباهم شدم اما نتوانستم نظر ارسالی را حذف کنم.
      ریشارپر من رو به اشتباه انداخت. این متد داخل کلاس GuardExtensions هستش و تازه الان پیداش کردم.
  • #
    ‫۲ سال و ۶ ماه قبل، دوشنبه ۲ اسفند ۱۴۰۰، ساعت ۱۸:۱۰
    جهت اطلاع
    این پروژه به ASP.NET Core 6.0 ارتقاء داده شد.
  • #
    ‫۲ سال و ۵ ماه قبل، شنبه ۲۱ اسفند ۱۴۰۰، ساعت ۱۱:۵۸
    با تشکر؛ کدهای این مطلب در BlazorServer پیاده سازی شده و در آدرس زیر قابل دسترس می‌باشد.
    تغییرات خیلی زیاد نیست فقط سعی شده کدهای این مطلب با مکانیزم کاری BlazorServer سازگار شود.
    • #
      ‫۱ سال و ۸ ماه قبل، دوشنبه ۲۱ آذر ۱۴۰۱، ساعت ۱۷:۵۹
      مشکلی در پیاده سازی BlazorServerCookieAuthentication  پیدا کرده ام.
      برای اینکه بتوانم در سرویسی از برنامه که اطلاعات کاربری لاگ می‌شود به آی دی کاربر دسترسی داشته باشم از کد زیر استفاده کردم که قبلا در SO راهنمایی کرده بودید بنده را. 
      using System.Security.Claims;
      using Microsoft.AspNetCore.Components.Authorization;
      using Microsoft.AspNetCore.Components.Server;
      
      namespace BlazorServerTestDynamicAccess.Services;
      
      public class CustomAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
      {
          private readonly IServiceScopeFactory _scopeFactory;
      
          public CustomAuthenticationStateProvider(ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory)
              : base(loggerFactory) =>
              _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
      
          protected override TimeSpan RevalidationInterval { get; } = TimeSpan.FromMinutes(30);
      
          protected override async Task<bool> ValidateAuthenticationStateAsync(
              AuthenticationState authenticationState, CancellationToken cancellationToken)
          {
              // Get the user from a new scope to ensure it fetches fresh data
              var scope = _scopeFactory.CreateScope();
              try
              {
                  var userManager = scope.ServiceProvider.GetRequiredService<IUsersService>();
                  return await ValidateUserAsync(userManager, authenticationState?.User);
              }
              finally
              {
                  if (scope is IAsyncDisposable asyncDisposable)
                  {
                      await asyncDisposable.DisposeAsync();
                  }
                  else
                  {
                      scope.Dispose();
                  }
              }
          }
      
          private async Task<bool> ValidateUserAsync(IUsersService userManager, ClaimsPrincipal? principal)
          {
              if (principal is null)
              {
                  return false;
              }
      
              var userIdString = principal.FindFirst(ClaimTypes.UserData)?.Value;
              if (!int.TryParse(userIdString, out var userId))
              {
                  return false;
              }
      
              var user = await userManager.FindUserAsync(userId);
              return user is not null;
          }
      }
      حال مشکل اینجاست که اگر CustomAuthenticationStateProvider را به صورت AddSingleton ثبت کنم با این مشکل روبرو می‌شوم. اگر هم که به صورت AddScoped ثبت کنم با مشکل دیگری روبرو می‌شوم. اگر ممکن است لطفا راهنمایی فرمایید.
        • #
          ‫۱ سال و ۸ ماه قبل، سه‌شنبه ۲۹ آذر ۱۴۰۱، ساعت ۱۴:۵۷
          ممنون. این راه را نیز قبلا امتحان کرده بودم در کدهای لوکال البته به شکل زیر:
          public async Task<int?> GetUserIdAsync()
                  {
                      var authenticationState = await _authenticationStateProvider.GetAuthenticationStateAsync();
                      if (!authenticationState.User.Identity.IsAuthenticated)
                          return 0;
                      return Convert.ToInt32(authenticationState.User.Claims?.First().Value);
                  }
           اما باز هم در صورتی که AuthenticationStateProvider به صورت AddScoped ثبت شود این خطا دریافت می‌گردد:
          GetAuthenticationStateAsync was called before SetAuthenticationState

            • #
              ‫۱ سال و ۷ ماه قبل، یکشنبه ۱۱ دی ۱۴۰۱، ساعت ۱۲:۱۷
              بسیار ممنون. از این روش در پروژه دیگری استفاده کرده بودم و هرکاری می‌کردم بازهم خطای مذبور را دریافت می‌کردم. پس از چند روز مقایسه کدها، کاشف به عمل آمد که مشکل از نوع تزریق سرویس در کامپوننت می‌باشد. در پروژه برای تزریق سرویس از این روش استفاده کرده بودم:
              @inherits OwningComponentBase 
              ...
              private IUserInfoService userInfoService { get; set; } 
              ...
              userInfoService = ScopedServices.GetRequiredService<IUserInfoService>();
              که با تغییر آن به شکل زیر مشکل در سطح کامپوننت حل شد:
              @inject IUserInfoService UserInfoService
              اما مشکل هنوز در سطح سرویس وجود دارد. یعنی اگر از سرویس UserInfoService در سرویس دیگر به روش زیر استفاده شود، آنگاه خطا بروز می‌کند.
              public class ManualLog: IManualLog
                  {
                  private readonly IUserInfoService _userInfoService;
              
                  public ManualLog(IUserInfoService userInfoService)
                          {
                              _userInfoService = userInfoService ?? throw new ArgumentNullException(nameof(userInfoService));
                          }
              
                  public async task Log()
                          {
                              int userId = await _userInfoService.GetUserIdAsync();
                           }
                  }
              پیام خطا مثل قبل:
               GetAuthenticationStateAsync was called before SetAuthenticationState   
              • #
                ‫۱ سال و ۷ ماه قبل، یکشنبه ۱۱ دی ۱۴۰۱، ساعت ۱۶:۲۵
                سرویس مدنظر باید در چرخه‌ی اعتبارسنجی صفحه ظاهر شود و نه خارج از آن؛ در غیراینصورت استفاده از آن بی‌معنا است.
                • #
                  ‫۱ سال و ۷ ماه قبل، یکشنبه ۱۱ دی ۱۴۰۱، ساعت ۱۷:۱۹
                  ممکن است بیشتر توضیح دهید؟
                  یعنی من نمی‌توانم از سرویس دیگری داخل سرویس فراخوانی شده توسط کامپوننت استفاده کنم؟
                  در کل رویکرد پروژه این بوده است که بتوان موقع لاگ گیری کد کاربر را نیز لاگ نمود. یک راه حل دیگر که به نظرم رسید این بود که کد کاربر را موقع فراخوانی سرویس از داخل کامپوننت مقداردهی کنم (با این روش)، اما اگر روش بالا جواب می‌داد کد تمیزتری حاصل می‌شد.
  • #
    ‫۲ سال قبل، دوشنبه ۷ شهریور ۱۴۰۱، ساعت ۱۹:۰۴
    در کامپوننت‌های blzaor server برای اینکه بتوان از claim زیر بهره برد:
    identity.AddClaim(new Claim("DisplayName", user.DisplayName));
    برای مثال زمانی که کاربر ورود را انجام داد به جای نام کاربری، نام نمایشی آن نشان داده شود. می‌توان از کد زیر در کامپوننت بهره برد:
     @context.User.Claims.Where(c => c.Type == "DisplayName").FirstOrDefault().Value.ToString()  
  • #
    ‫۱ سال و ۷ ماه قبل، دوشنبه ۲۶ دی ۱۴۰۱، ساعت ۰۳:۳۳
    یک سوال: اینکه به ازای هر درخواست، اطلاعات کاربر از دیتابیس گرفته میشه، سربار اضافی برای برنامه نداره؟ از لحاظ پرفرمنس زمانی و درگیر کردن پایگاه داده.
    بهتر نیست securityStamp کاربر رو در رم (مثلا ردیس) نگهداری کنیم و در هر درخواست ، صرفا اونو چک کنیم؟
    • #
      ‫۱ سال و ۷ ماه قبل، دوشنبه ۲۶ دی ۱۴۰۱، ساعت ۰۳:۴۵
      - خیر. یافتن یک کاربر بر اساس ID آن که کلید اصلی جدول را هم تشکیل می‌دهد و بر روی آن ایندکس وجود دارد، فوق العاده سریع است. یک چنین کوئری گرفتنی در ASP.NET Core Identity هم وجود دارد. البته ASP.NET Core Identity برای انجام اینکار، بیش از یک کوئری را صادر می‌کند.
      - وجود ICookieValidatorService که سبب بروز این کوئری اضافه می‌شود، در جهت پیاده سازی logout آنی و بررسی به روز بودن محتوای کوکی دریافتی، ضروری هست. نیاز هست تا بتوان مقدار public string SerialNumber مطلب جاری را بین اطلاعات موجود در سرور و اطلاعات قبلی موجود در کوکی صادر شده‌ی در حین لاگین، مقایسه کرد. اگر این بررسی صورت نگیرد، تا زمانیکه طول عمر کوکی کاربر به پایان نرسیده، با موفقیت و بدون هیچ کوئری اضافه‌ای، اطلاعات موجود در آن، در سمت سرور اعتبارسنجی شده و استفاده می‌شود. از آن اطلاعات در جهت تامین خودکار user claims و roles کاربر جاری استفاده می‌شود (همان شیء this.User قابل دسترسی در اکشن متدها)؛ چون زیر ساخت ASP.NET Core در جهت بررسی اطلاعات کوکی رسیده، به همراه دریافت اطلاعات مجدد آن از بانک اطلاعاتی سمت سرور نیست. اگر کوکی را معتبر تشخیص دهد و هنوز طول عمر آن به پایان نرسیده باشد، از اطلاعات آن در جهت ساخت شیء this.User استفاده می‌کند. اینجا است که اهمیت وجودی ICookieValidatorService در جهت تامین یک لایه‌ی اضافی امنیتی بررسی محتوای کوکی دریافتی، مشخص می‌شود. برای مثال فرض کنید که کاربری را به دلیل خاصی غیرفعال یا ممنوع الورود کرده‌اید. اگر سرویس ICookieValidatorService را نداشته باشید، این کاربر تا زمانیکه طول عمر کوکی آن معتبر تشخیص داده شود، می‌تواند در سایت خراب کاری کند؛ چون اطلاعات کوکی او، شیء this.User معتبری را تولید می‌کند که وابسته‌ی به اطلاعات جدید بانک اطلاعاتی برنامه نیست. همچنین قرار دادن دستی بررسی دوباره‌ی اعتبار user claim حاصل از اطلاعات کوکی کاربر جاری در هر اکشن متد و مقایسه‌ی آن با اطلاعات جدید بانک اطلاعاتی، سبب شلوغ شدن و تکرار کدهای اضافه‌ای می‌شود که به صورت متمرکز و یکجا در ICookieValidatorService وجود دارد. نمونه‌ی دیگر کاربرد این سرویس، پیاده سازی خودکار مفهوم single sign out است؛ به این معنا که اگر کاربری logout کرد، بتوان با تغییر SerialNumber او در سمت سرور، سایر افرادی را که در همان لحظه از اکانت او (کوکی‌های معتبر او) در سیستم‌های دیگری استفاده می‌کنند، logout کرد.
      - استفاده از کش، به همراه بحث invalidation دقیق آن هم در صورت بروز تغییرات در بانک اطلاعاتی هست. هستند کتابخانه‌هایی که این‌کارها را به صورت خودکار و بدون نیازی به تغییری در کدها برای شما انجام دهند.