简体   繁体   中英

Configuring cookies in Asp.Net Core 3.1 with Identity

I'm using Asp.Net Core 3.1 with Identity. And here is my all configuration in Startup class. I'm trying to force the logged in user to logout if their accounts expired while using the app. I should configure the cookie correctly but i'm stuck on how to do this while having AddIdentity.

Here is my Startup

 // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
        services.AddDbContext<ApplicationDbContext>(options =>
           options.UseSqlServer(
               Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<IdentityUser, IdentityRole>(options => {
            options.SignIn.RequireConfirmedAccount = false;
        })
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders(); 

        services.AddIdentityCore<ApplicationUser>()
            .AddRoles<IdentityRole>()
            .AddClaimsPrincipalFactory<UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            //.AddDefaultTokenProviders()  
            .AddDefaultUI(); 

        services.AddSingleton<IEmailSender, EmailSender>();
        services.Configure<EmailOptions>(Configuration);
   
        services.AddHangfire(config => config.UseSqlServerStorage(Configuration.GetConnectionString("DefaultConnection")));
        services.AddHangfireServer();

        services.AddControllersWithViews(); //?
        services.AddRazorPages().AddRazorRuntimeCompilation(); //?
        services.AddScoped<IExpirationJob, ExpirationJob>();
        services.AddScoped<IReminderJob, EmailReminder>();
        services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
        services.Configure<IdentityOptions>(options =>
        {
            // Password settings.
            //options.Password.RequireDigit = true;
            //options.Password.RequireLowercase = true;
            //options.Password.RequireNonAlphanumeric = true;
            //options.Password.RequireUppercase = true;
            //options.Password.RequiredLength = 6;
            //options.Password.RequiredUniqueChars = 1;

            // Lockout settings.
            //options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
            //options.Lockout.MaxFailedAccessAttempts = 5;
            //options.Lockout.AllowedForNewUsers = true;

            // User settings.
            //options.User.AllowedUserNameCharacters =
            //    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+ ";
            //options.User.RequireUniqueEmail = false;
        });

        services.ConfigureApplicationCookie(config =>
        {
            config.Cookie.Name = "my.Cookie";
            config.LoginPath = "/Home/Login";
            config.AccessDeniedPath = "/Identity/Account/AccessDenied";
            config.Cookie.HttpOnly = true;
            config.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
        });
     }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app,
        IWebHostEnvironment env,
        IRecurringJobManager recurringJobManager,
        IServiceProvider serviceProvider)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();


        app.UseHangfireDashboard();
        //app.UseHangfireDashboard("/hangfire", new DashboardOptions()
        //{
        //    Authorization = new[] { new CustomAuthorizeFilter() }
        //});

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();
      
     
        app.UseAuthentication();
        app.UseAuthorization();
      
        recurringJobManager.AddOrUpdate(
           "End Users Subscription",
           () => serviceProvider.GetService<IExpirationJob>().SetExpired(),
           Cron.Minutely
           );

        recurringJobManager.AddOrUpdate(
           "Send End of Subscription Reminder",
           () => serviceProvider.GetService<IReminderJob>().SendReminder(),
           Cron.Daily
           );

        app.Use(async (context, next) =>
        {
            _ = ExpirationJob.SetExpired(context);//pass the HttpContext to SetExpired
            await next();
        });

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            endpoints.MapRazorPages();
        });
    }

Here is my ValidateAsync class

 public class ValidateAsync
{
    public static async Task ValidatingAsync(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<IdentityUser> userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<IdentityUser>>();
        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);
        }
    }
}

Here is my SetExpired method

      public interface IExpirationJob
{
    Task SetExpired();

}
public class ExpirationJob : IExpirationJob
{
    private readonly ApplicationDbContext _db;
    private readonly IEmailSender _emailSender;
    private readonly HttpContext _context;

    public ExpirationJob(ApplicationDbContext db, IEmailSender emailSender, HttpContext context)
    {
        _db = db;
        _emailSender = emailSender;
        _context = context;
    }

    public async Task SetExpired()
    {
        foreach(var item in _db.Institution)
        {
            if (item.SubsEndDate != null)
            {
                if (item.SubsEndDate <= DateTime.Now) 
                {
                   
                    item.Status = SD.StatusExpired;

                    Guid securityStamp = Guid.NewGuid();
                    item.Admin.SecurityStamp = securityStamp.ToString();

                    _context.Response.Cookies.Append("my.Cookie", "expired");
                }
            }
        }
        await _db.SaveChangesAsync();
    }
}

After understanding the business logic, you can get the HttpContext in the middleware, and modify the cookie value to force logout. Here is an example.

First, configure basic cookies in ConfigureServices .

 services.ConfigureApplicationCookie(config =>
        {
            config.Cookie.Name = "my.Cookie";
            config.LoginPath = "/Home/Login";
            config.AccessDeniedPath = "/Identity/Account/AccessDenied";
            config.Cookie.HttpOnly = true;
            config.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
        });

Second, configure the middleware.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
       IExpirationJob expirationJob)
    {
        //...
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();
        app.Use(async(context,next)=>
        {
            expirationJob.SetExpired(context);//pass the HttpContext to SetExpired
            await next();
        });
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }

Three, determine whether to overwrite the cookie by the expiration time SubscriptionEndDate of the database.

public async Task SetExpired(HttpContext httpContext)
    {
        foreach (var item in _db.Institution)
        {
            if (item.SubscriptionEndDate != null)
            {
                if (item.SubscriptionEndDate == DateTime.Today)
                {
                    item.Status = SD.StatusExpired;
                    Guid securityStamp = Guid.NewGuid();
                    item.SecurityStamp = securityStamp;
                    
                    httpContext.Response.Cookies.Append("my.Cookie", "expired");
                }
            }
        }
        await _db.SaveChangesAsync();
    }

Then, the cookie is invalid and this user will not access the resource.

在此处输入图片说明

Update:

An another method, if you want to pass the HttpContext to SetExpired as a parameter.

  1. You can write a static class, like this:
public static class MyClass
    {
        public static HttpContext http { get; set; }
    }
  1. Then, you can assign values to http in Statup.cs .

     public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IExpirationJob expirationJob) { //... app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.Use(async(context,next)=> { MyClass.http = context; expirationJob.SetExpired(); await next(); }); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); }
  2. Get it with this.

     public async Task SetExpired() { var path=MyClass.http.Request.Path; //foreach (var item in _db.Institution) //{ // if (item.SubsEndDate != null) // { //... await _db.SaveChangesAsync(); }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM