1

I am trying to adjust my .NET Core 6 project to use a session timeout that is not the default.

Following the instructions on Microsoft.com, I have tried adding the following to my Program.cs:

//This should cause me to time out after 1 minute. It does not
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(
    options => {
        options.Cookie.Name = ".MyWebsite.Session";
        options.IdleTimeout = TimeSpan.FromMinutes(1);
        options.Cookie.IsEssential = true;
    });

app.UseSession();

Is there something I am missing? I would like to increase the time it takes for a session to expire. As per the code above, when I log in to the program, my session should expire after 1 minute. It does. Same if I set the time to 4 hours. It seems to force me to log out around the 20 minute mark regardless of the settings I use.

The project was migrated from .NET Core 3.1, if that matters.

Here is the full code from the Program.cs file:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("MyConnection");
builder.Services.AddDbContext<DBContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddControllersWithViews();

builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(
    options => {
        options.Cookie.Name = ".MyWebsite.Session";
        options.IdleTimeout = TimeSpan.FromMinutes(1);
        options.Cookie.IsEssential = true;
    });

//Add JWT bearer and denied paths
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.LoginPath = "/Account/Unauthorized/";
        options.AccessDeniedPath = "/Account/Forbidden/";
    })
            .AddJwtBearer(x =>
            {
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = false,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = builder.Configuration["Jwt:Issuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("Jwt:Key"))
                };
            });

//GDPR compliance
builder.Services.Configure<CookiePolicyOptions>(options =>
{
    options.CheckConsentNeeded = context => true;
    options.MinimumSameSitePolicy = SameSiteMode.None;
});

builder.Services.ConfigureNonBreakingSameSiteCookies();

builder.Services.ConfigureApplicationCookie(options =>
{
    options.Events = new CookieAuthenticationEvents
    {
        OnRedirectToLogin = x =>
        {
            x.Response.Redirect("https://localhost:44329/Expired/Index/000");
            return Task.CompletedTask;
        }
    };
    options.ExpireTimeSpan = TimeSpan.FromDays(14);
    options.SlidingExpiration = true;
});

//define policy for different authorization
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Administrator"));
    options.AddPolicy("UsersOnly", policy => policy.RequireRole("User", "Editor", "Administrator"));
    options.AddPolicy("RequireApprovedUser", policy => policy.Requirements.Add(new ApprovedUserRequirement(true)));
});

builder.Services.AddScoped<IAuthorizationHandler, ApprovedUserRequirementHandler>();

//Data Protection configuration
var keysFolder = Path.Combine(builder.Environment.ContentRootPath, "Keys");
builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(keysFolder))
    .SetDefaultKeyLifetime(TimeSpan.FromDays(14));

builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(6);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.SignIn.RequireConfirmedAccount = true;
})
    .AddDefaultTokenProviders()
    .AddDefaultUI()
    .AddEntityFrameworkStores<DBContext>();

builder.Services.AddRazorPages();

builder.Services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, UserClaimsPrincipalFactory<IdentityUser, IdentityRole>>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}
app.Use(async (context, next) =>
{
    await next();
    if (context.Response.StatusCode >= 400)
    {
        context.Request.Path = "/Error/Index/" + context.Response.StatusCode;
        await next();
    }
});

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseSession();

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

app.Run();

8
  • How do you check that session cookie lives not long enough? Commented Jan 12, 2023 at 22:36
  • I don't know. That's why I'm asking. Commented Jan 12, 2023 at 22:39
  • You are making the following claim - "but it stays the same no matter what I set it to.", to make it you need to check how long the cookie lives. So how you can't know how do you check that if you make such claim? Commented Jan 12, 2023 at 22:42
  • I test it by logging in. After 1 minute of inactivity, it allows me to stay logged in, despite the session time limit being set at 1 minute. However, after 20 minutes, I am forced to log in again. Presumably because the session expired. I do not know why it does this. If you know, please write an answer. Commented Jan 12, 2023 at 22:51
  • 1
    I'm pretty sure that authentication is handled by another cookie, not the session one (if you are using cookie based auth). Commented Jan 12, 2023 at 22:56

4 Answers 4

2

When using cookie authentication, the ExpireTimeSpan property is only used if when calling HttpContext.Authentication.SignInAsync we pass in an instance of AuthenticationProperties with IsPersistent set to true.

So, you may want the cookie to persist across browser sessions. This persistence should only be enabled with explicit user consent with a "Remember Me" checkbox on sign in or a similar mechanism. The code like this:

// using Microsoft.AspNetCore.Authentication;

await HttpContext.SignInAsync(
    CookieAuthenticationDefaults.AuthenticationScheme,
    new ClaimsPrincipal(claimsIdentity),
    new AuthenticationProperties
    {
        IsPersistent = true
    });

Besides, you can also set Absolute cookie expiration. The following code snippet creates an identity and corresponding cookie that lasts for 20 minutes. This ignores any sliding expiration settings previously configured.

// using Microsoft.AspNetCore.Authentication;

await HttpContext.SignInAsync(
    CookieAuthenticationDefaults.AuthenticationScheme,
    new ClaimsPrincipal(claimsIdentity),
    new AuthenticationProperties
    {
        IsPersistent = true,
        ExpiresUtc = DateTime.UtcNow.AddMinutes(20)
    });

More detail information, see Persistent cookies.

Sign up to request clarification or add additional context in comments.

3 Comments

This seems to work. I'll have to do some more tests, waiting 20-30 minutes to see if the session is still alive is arduous, but otherwise it looks like this does it.
Unfortunately, this does not work for me. I've set ExpiresUtc = DateTime.UtcNow.AddHours(24) and I am still logged out around the 30-minute mark.
Try to use F12 developer Application tool to check the cookie Expires/Max-Age, I also created a sample and checked the code on my side, it seems that the cookie Expires/Max-Age is correct.
2

If you want to configure session timeout in Identity you can use

builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Identity/Account/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true; 
});

https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0&tabs=visual-studio

1 Comment

Thank you for contributing to the Stack Overflow community. This may be a correct answer, but it’d be really useful to provide additional explanation of your code so developers can understand your reasoning. This is especially useful for new developers who aren’t as familiar with the syntax or struggling to understand the concepts. Would you kindly edit your answer to include additional details for the benefit of the community?
0

Welp, I finally figured it out. Turns out using sessions for my authentication cookies just don't work. Probably something I did elsewhere.

I used the following to force my cookies to have a lifetime of 1 minute:

builder.Services.ConfigureApplicationCookie(options =>
{
    options.Cookie.Name = ".AspNetCore.Identity.Application";
    options.ExpireTimeSpan = TimeSpan.FromMinutes(1);
    options.Cookie.MaxAge = TimeSpan.FromMinutes(1);
    options.SlidingExpiration = true;
});

Key here is to use options.Cookie.MaxAge in addition to options.ExpireTimeSpan.

Giving users more than the 20-odd minute default time is much easier now.

1 Comment

Looks like this method doesn't work. It reduces session time, but does not increase it.
0

I believe the problem is in the 'Expiration' setting (IdleTimeout). I set e.g. 1 hour like the others, but in DevTools->Application->Cookies the expiration was still 2h less than the current time.

Upon closer inspection, I found that the cookie time is in GMT/UTC format. That is, for Czech Republic (UTC+1:00 (winter) and UTC+2:00 in summer). The cookie was set to 1 hour, it was already expired at launch.

Well, for me the solution was to set (UTC+2) + (IdleTimeout +1) = 3h.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.