Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core website timing out after 30 minutes

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?

like image 766
ganders Avatar asked Feb 01 '17 14:02

ganders


1 Answers

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.

like image 71
truemedia Avatar answered Oct 10 '22 00:10

truemedia