I've got an ASP.NET Core MVC app, hosted on Azure websites, where I've implemented Session and Identity. My problem is, after 30 minutes, I get logged out. It doesn't matter if I've been active in the last 30 minutes or not.
Doing some searching, I found that the issue is the SecurityStamp stuff, found here. I've tried implementing this by doing the following:
Here's my UserManager impelmentation with the security stamp stuff:
public class UserManager : UserManager<Login>
{
public UserManager(
IUserStore<Login> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<Login> passwordHasher,
IEnumerable<IUserValidator<Login>> userValidators,
IEnumerable<IPasswordValidator<Login>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<Login>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
// noop
}
public override bool SupportsUserSecurityStamp => true;
public override async Task<string> GetSecurityStampAsync(Login login)
{
return await Task.FromResult("MyToken");
}
public override async Task<IdentityResult> UpdateSecurityStampAsync(Login login)
{
return await Task.FromResult(IdentityResult.Success);
}
}
Here's my ConfigureServices method on Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddSingleton(_ => Configuration);
services.AddSingleton<IUserStore<Login>, UserStore>();
services.AddSingleton<IRoleStore<Role>, RoleStore>();
services.AddIdentity<Login, Role>(o =>
{
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequiredLength = 6;
o.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromDays(365);
o.Cookies.ApplicationCookie.SlidingExpiration = true;
o.Cookies.ApplicationCookie.AutomaticAuthenticate = true;
})
.AddUserStore<UserStore>()
.AddUserManager<UserManager>()
.AddRoleStore<RoleStore>()
.AddRoleManager<RoleManager>()
.AddDefaultTokenProviders();
services.AddScoped<SignInManager<Login>, SignInManager<Login>>();
services.AddScoped<UserManager<Login>, UserManager<Login>>();
services.Configure<AuthorizationOptions>(options =>
{
options.AddPolicy("Admin", policy => policy.Requirements.Add(new AdminRoleRequirement(new RoleRepo(Configuration))));
options.AddPolicy("SuperUser", policy => policy.Requirements.Add(new SuperUserRoleRequirement(new RoleRepo(Configuration))));
options.AddPolicy("DataIntegrity", policy => policy.Requirements.Add(new DataIntegrityRoleRequirement(new RoleRepo(Configuration))));
});
services.Configure<FormOptions>(x => x.ValueCountLimit = 4096);
services.AddScoped<IPasswordHasher<Login>, PasswordHasher>();
services.AddDistributedMemoryCache();
services.AddSession();
services.AddMvc();
// repos
InjectRepos(services);
// services
InjectServices(services);
}
And lastly, here's my Configure method on Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseApplicationInsightsRequestTelemetry();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/home/error");
}
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseSession();
app.UseIdentity();
app.UseMiddleware(typeof (ErrorHandlingMiddleware));
app.UseMiddleware(typeof (RequestLogMiddleware));
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
What's wrong with my implementation here?
UPDATE: What a second...I noticed my UserManager is not inheriting from any interfaces for the security stamp stuff, is that what's needed?
This is simply because you need to enable and configure Data Protection. The cookie and session setup looks correct. What is happening right now for you is that whenever the app is recycled or the server load balances to another server or a new deployment happens, etc, it creates a new Data protection key in memory, so your users' session keys are invalid. So all you need to do is add the following to Startup.cs:
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"D:\writable\temp\directory\"))
.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
Use the documentation to learn how to properly set this up and the different options of where to save the Data Protection key (file system, redis, registry, etc). You could think of the data protection key as the replacement of the web.config's machine key in asp.net.
Since you mentioned you're using Azure, you could use this package Microsoft.AspNetCore.DataProtection.AzureStorage
to save the key so that it persists. So you could use this example of how to use Azure Storage.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With