Final Update: It turned out that the issue was caused by authentication timeouts rather than session timeouts. These are different things and need to be set separately. Here is the fix I ended up with:
Create a timeout value in appsettings.json. This value will be used to configure session and authorization timeouts. (You can name this whatever you want.)
"IdleTimeoutMinutes": 60,
Set session timeout in Startup.cs.
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(Configuration.GetValue<double>("IdleTimeoutMinutes"));
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
Configure authentication timeout in Startup.cs.
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate()
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.LoginPath = new PathString("/Account/Login/LogOn/");
options.AccessDeniedPath = new PathString("/Account/Login/AccessDenied/");
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(Configuration.GetValue<double>("IdleTimeoutMinutes"));
});
Configure authentication cookie timeout in LoginController.cs.
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme, claims.Get(currentUser),
new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(Configuration.GetValue<double>("IdleTimeoutMinutes")),
IsPersistent = true,
AllowRefresh = true
});
I found this information here: https://www.blakepell.com/blog/asp-net-core-cookie-authentication-timing-out
(Archived page: https://web.archive.org/web/20220411151611/https://www.blakepell.com/blog/asp-net-core-cookie-authentication-timing-out)
Note that the Cookie.Expiration
setting described at this link no longer works. It returns a error "Cookie.Expiration is ignored, use ExpireTimeSpan instead". I left out that line and everything else worked.
Update: As a test, I changed the session IdleTimeout value from 30 minutes to 10 seconds and discovered that the session timeout is, in fact, working as expected. There is a different, unknown, timeout that is breaking things after 15 minutes, regardless of the session IdleTimeout value. Where is this 15 minute timeout coming from?
Things that break after IdleTimeout seconds:
HttpContext.Session.GetString(SessionKeySheetData)
in .cs code returns a "Value cannot be null" error. This is expected. The session IdleTimeout is working correctly.
Things that break after 15 minutes idle, regardless of the session IdleTimeout value:
- JavaScript Ajax calls result in an authentication popup asking for user name and password. This does not occur anywhere else in this application that uses Windows authentication and it does not accept my usual Windows login user name and password. I can work around this by adding a
[AllowAnonymous]
directive to the relevant code. Context.Request.Form("<form field id>")
in .cshtml code returns a "Incorrect Content-type" error.
Apparently there are two idle timeouts here. The session timeout, controlled by the IdleTimeout value, is working as expected. But there is another timeout of 15 minutes that is not related to the session timeout. Where is this other timeout coming from and how can it be changed?
Original question:
I'm a beginning C# coder and I have been tasked with maintaining an existing web application written in C# with .NET 5.0. The current session timeout seems to be 15 minutes and I would like to increase it. This question has been asked and answered many times, but before flagging this as a duplicate, understand that most of those questions and answers are for older versions of .NET and are not applicable to the current version. Also, nearly everyone has a different answer, and I have not been able to get any of them to work.
The answers I have found fall into four categories:
IIS configuration changes. For development I am using the IIS Express that comes with VS 2019. I have installed the latest IIS Manager app, but nothing I see there matches the answers I find online. I presume those answers are for older versions of IIS, and I do not see any session timeout configuration in the current version of IIS Manager. Example: https://beansoftware.com/ASP.NET-Tutorials/Session-Timeout-Expiration.aspx
Project configuration changes. I have found many, many pages suggesting to set
sessionState timeout="<minutes>"
in web.config. However my project does not have a web.config and it is my understanding that this is used only in older .NET versions. Example: Session timeout in ASP.NETCreate a JavaScript/Ajax function to keep the session alive by calling a dummy HTTP Handler. I haven't been able to get this to work and it seems like an ugly hack if there is a way to simply change the timeout configuration. Example: Keeping ASP.NET Session Open / Alive
Change the session timeout programmatically. I think this would be a good option if I could get it to work, but adding
public TimeSpan IdleTimeout = TimeSpan.Parse("01:00:00");
to the code does not seem to have any effect. Example: https://learn.microsoft.com/en-us/dotnet/api/system.web.configuration.processmodelsection.idletimeout?view=netframework-4.8
Any help would be appreciated.
Edit: Added Startup.cs below, as requested.
using ILawProductionApp.Domain.Context;
using ILawProductionApp.Domain.Helpers.Sheets;
using ILawProductionApp.Domain.Shared.Contracts;
using ILawProductionApp.Domain.Shared.Repository;
using ILawProductionApp.Infrastructure.Contexts;
using ILawProductionApp.Infrastructure.Interfaces;
using ILawProductionApp.Infrastructure.Interfaces.Repositories;
using ILawProductionApp.UI.Areas.Account.Managers.Authorization;
using ILawProductionApp.UI.Helpers;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
namespace ILawProductionApp.UI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate()
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.LoginPath = new PathString("/Account/Login/LogOn/");
options.AccessDeniedPath = new PathString("/Account/Login/AccessDenied/");
});
services.AddDbContext<AppIdentityDbContext>(options => options.
UseOracle(Environment.GetEnvironmentVariable(Configuration["EnvironmentVars:DefaultConnectionString"])));
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddSingleton<IAuthorizationPolicyProvider, LawPermissionPolicyProvider>();
services.AddScoped<IAuthorizationHandler, RolesAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, LawPermissionAuthorizationHandler>();
//services.AddScoped<IAuthorizationHandler, ProcessCalApp2AuthorizationHandler>();
//services.AddScoped<IAuthorizationHandler, ProccessGlassAppAuthorizationHandler>();
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(Configuration.GetValue<double>("SessionTimeoutSeconds"));
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
services.AddControllersWithViews().AddSessionStateTempDataProvider();
services.AddDbContext<ApplicationILawDbContext>(options => options.UseLazyLoadingProxies()
.UseOracle(Environment.GetEnvironmentVariable(Configuration["EnvironmentVars:DefaultConnectionString"])));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IAdminUnitOfWork, AdminUnitOfWork>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<ICa2RunUtil, Ca2RunUtil>();
services.AddScoped<ILawGaRunUtil, LawGaRunUtil>();
services.AddTransient<ISheetModifier, SheetModifier>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
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.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "Adminstration",
pattern: "{area:exists}/{controller=Admin}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "Account",
pattern: "{area:exists}/{controller=Account}/{action=Index}/{id?}"
);
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
}
}