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.NET
Create 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();
});
}
}
}
Related
I'm working on a project whose back-end is built in .NET 3.1 and Angular 11 for the front-end. I'm trying to set the HttpOnly attribute on true for cookies via the Startup method, which is extended by various APIs and Managers through the entire application: so far the current configuration is as follows.
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<CookiePolicyOptions>(options =>{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
options.HttpOnly = HttpOnlyPolicy.Always;
options.Secure = CookieSecurePolicy.Always;
});
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseCookiePolicy();
...
}
Anyways, when checking via the Network tab in Chrome/Firefox the HttpOnly flag is not set, what am I missing?
Thanks for any help!
It seems like you need to pass in the cookie policy options you created.
Using this line
services.Configure<CookiePolicyOptions>(options ...
makes the cookie policy available later on but doesn't configure it in your startup.
According to https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1#cookie-policy-middleware-1 you'll need to create a cookie policy and then pass that in to
app.UseCookiePolicy();
Otherwise, it uses the default cookie policy which doesn't have them set to what you would prefer.
CheckMarx is flagging an error which looks like a false positive to me. Our application is written in C# and uses ASP.NET Core.
The error is:
The web application's Startup method creates a cookie Startup, at line 22 of Startup.cs, and returns it in the response. However, the application is not configured to automatically set the cookie with the "httpOnly" attribute, and the code does not explicitly add this to the cookie.
This is line 22:
public class Startup
And we do have the cookie policy set correctly:
app.UseCookiePolicy(new CookiePolicyOptions
{
HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always
});
But CheckMarx is still flagging this warning. And I do not think that my Startup class creates a cookie called Startup.
I found a similar post here (unanswered) - https://github.com/Azure/azure-functions-vs-build-sdk/issues/469.
So is this a false positive? And how do I get CheckMarx to stop flagging it?
The only way to remove those warnings was to rename the Startup class to something else, for example to Startup123.
Nothing else removes the warning, and I think it is definitely a false positive.
For .NET Core 3.1, I fixed this vulnerability warning by configuring the service in Startup class and then using CookiePolicy middleware.
In ConfigureServices function:
services.Configure<CookiePolicyOptions>(options =>
{
options.Secure = CookieSecurePolicy.Always;
});
In Configure function:
app.UseCookiePolicy();
This could be also used to fix HttpOnlyPolicy vulnerability in middleware like:
services.Configure<CookiePolicyOptions>(options =>
{
options.HttpOnly = HttpOnlyPolicy.Always;
options.Secure = CookieSecurePolicy.Always;
});
Remember to use the correct order for middlewares. You could refer to ASP.NET Core Middleware Docs to read more about and get some examples.
I am new is asp.net core
I am trying to create web application using asp.net core 3.1 as per the instruction on link
i created login page which is working, application login is working. so after login 1st user in application i copied the cookies of 1st user to other browser and open localhost site and i saw user got loggedin without authentication.
is this right implementation how to create safe login and authorization module in asp.net core webapp
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
// Add Distributed Redis Cache for Session
services.AddDistributedRedisCache(options =>
{
options.Configuration = "localhost";
options.InstanceName = "Session_";
});
services.AddSession(options =>
{
// 20 minutes later from last access your session will be removed.
options.IdleTimeout = TimeSpan.FromMinutes(20);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/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();
// Adds session middleware to pipeline
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
sample code from link
Let's summarize a few things here:
First of all, if your web application is configured correctly it is not possible to steal someone else's cookie. Unless you're on the same physical machine (which I assume is what you did with when you copied the cookie) of course.
So your site should always be served over HTTPS, that's configured correctly: app.UseHttpsRedirection();. And cookies should be HttpOnly (meaning not accessible by javascript): options.Cookie.HttpOnly = true; (but that's for the session cookie). The cookie that is created by the Identity template is marked as HttpOnly and Secure by default so that's also fine. So basically the answer to your question is 'Yes, the Identity template is safe to use'
As a final comment I would recommend to add app.UseHsts(); to add HSTS headers for more security.
I'm trying to deploy an Angular 7/.net Core application on my local IIS and am running into an issue. I used the Angular template in Visual Studio to create a .net core backend with an Angular front-end. I also added SignalR to both projects. Here are some code samples:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddSignalR();
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/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.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseCors("CorsPolicy");
app.UseSignalR(routes =>
{
routes.MapHub<MyHub>("/myHub");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
connection.service.ts
if (!this.hubConnection) {
this.hubConnection = new
HubConnectionBuilder().withUrl('http://localhost:5000/myhub').build();
}
public start(): void {
this.hubConnection
.start()
.then(() => {
console.log('Connection started');
this.startingSubject.next();
})
.catch((error: any) => this.startingSubject.error(error));
}
data.component.ts
private getAllData(): Promise<Data> {
const publishDate = this.getPublishDate();
return this.connectionService.hubConnection.invoke("GetAllData",
publishDate);
}
As a quick summary, I have a connection service to handle the signalR connections on the Angular side. Essentially, app.component.ts calls the Start() method in connection.service.ts which starts the SignalR connection. data.component.ts is subscribed to this event and when the connection is successful, it calls the GetAllData() method.
I was trying to follow this tutorial in getting this set up via IIS, but can't get it to work. (https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.2)
I Publish from Visual Studio, this creates a directory with my .net Core DLL and a ClientApp folder for my Angular site. If I do a dotnet myapp.dll command I can navigate to localhost:5000 and everything works great.
localhost:5000/myhub returns a response from my signalR hub
localhost:5000/client shows the signalR client webpage perfectly
localhost:5000/host shows the signalR host webpage perfectly.
I should also note that this works when running through VS too. However, when I run through IIS, I get these results:
localhost:5000/myhub returns a response from my signalR hub
localhost:5000/client shows the signalR client webpage perfectly
localhost:5000/host fails with:
ERROR Error: Uncaught (in promise): Error: An unexpected error
occurred invoking 'GetAllData' on the server. Error: An unexpected
error occurred invoking 'GetAllData' on the server.
/Host does try to make a call to /myhub, which makes me wonder if IIS has an issue with this communicating with the same port or something. or maybe I'm just setting up IIS wrong.
Does anyone have any ideas as to how to get this working via IIS? I'm been scratching my head over this all afternoon.
Edit:
After continuing to troubleshoot, it looks like the data.component.ts is successfully calling a "Connect" method on the hub just before the "GetAllData" method.
public Data GetAllData(DateTime? publishDate)
{
... Logic here
}
PublishDate should allow nulls (in this scenario, null is actually being passed to this method), is it possible this isn't allowed for some reason? Again, i have a hard time seeing why this would work everywhere but IIS, but I'm grasping at straws that this point. Seems weird that Connect() would work but GetAllData() wouldn't when they're on the same hub.
One more edit
The more I research, the more it looks like there is an actual exception within the GetAllData() method. I'm working at verifying this but I think what's happening is that I have a file path that I'm trying to access but this file path doesn't exist when the application is built. I'm not 100% sure as to why it's failing for IIS only but I'm continuing to dig. I'll post my findings in case anyone else stumbles across this very specific issue :)
I may have missed it.. but where is your MyHub class?
Something like this:
Public class MyHub : Hub {
Public async Task GetAllData() {
*logic here for when client calls hub*
}
}
I'm new to coding in general and specifically Asp.Net core so excuse my noobiness here. I'm really hoping for some help with my project. I'm at the moment tearing my hair with adding different TimeSpan to different users while logging in. Say for example user from company 1 login and will, hopefully, get a session time of say 60 min (of idle time that is) but when user from company 2 login the session time would be maybe the default of 20 min.
UPDATE:
My Target framework is .Net Framework 4.6.1 but the code is prepared for .Net Core 2.1 and I'm running the Identity package v2.1.2
My startup class looks like this:
public class Startup
{
public Startup(IHostingEnvironment env)
{
CultureInfo.CurrentCulture = new CultureInfo("sv-SE");
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
builder.AddInMemoryCollection(GetBeanstalkEnvironmentProperties());
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ExternalServerConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<EmailConfiguration>(Configuration.GetSection("EmailConfiguration"));
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
Login in controller is just as easy as this:
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation(1, "User logged in.");
return RedirectToAction("...");
...
And after checking different solutions and forums a found this option to add the Timespan in Startup:
services.Configure<SecurityStampValidatorOptions>(options =>
{
// Add validationinterval E.g how often the stamp is checked.
options.ValidationInterval = TimeSpan.FromSeconds(10);
});
services.AddAuthentication().Services.ConfigureApplicationCookie(options =>
{
// Timespan extension if page is refreshed or navigated. Default is true.
options.SlidingExpiration = true;
// Cookie is valid at minimum 20 min.
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
});
This works fine but this is a global way of setting the session time for the users and will then apply to all users. So I hope someone have a good solution where I can use the above and still be able to add different values here options.ExpireTimeSpan = TimeSpan.FromMinutes(20); depending on the users preferences e.g. which company the user is a member of.
Other references and whats I've tried so far.
I've tried to figure this one out but did not succeed in my case. I guess I would need be first be able to check the user for claims (to see which company the user belongs to) and perhaps set a value for the specific company e.g. company 1 value 60 in a DB table then read that value and update the Timespan with that value from the Db or in a more "ugly" way just hardcode the value in Startup. Say that user belongs to a certain company which then should have a specific session value or if not set the default value of 20 min. For example in pseudo: (If user.companyID equals 1 then set options.ExpireTimeSpan = TimeSpan.FromMinutes(60)) and so on. My problem here is that of course I canĀ“t access this values in startup, and probably shouldn't?
I've also tried this but that extends the cookie for the client side if I didn't get i wrong and doesn't effect the idle user time?! and it also uses Session which I guess I could add but I'm not sure that will be any good in my situation anyway.
I've tried to follow this documentation as much as possible as well. But the framework doesn't seem to support multiple/different ExpireTimeSpan settings.
Here is another question, that has no answers yet, with what I can tell the same problem as I'm having.
I solved it like this instead:
services.Configure<SecurityStampValidatorOptions>(Configuration.GetSection("SecurityStampValidatorOptions"));
services.AddAuthentication().Services.ConfigureApplicationCookie(options =>
{
var cookieAuthenticationOptions = Configuration
.GetSection(nameof(CookieAuthenticationOptions))
.Get<CookieAuthenticationOptions>();
if (cookieAuthenticationOptions == null)
return;
options.ExpireTimeSpan = cookieAuthenticationOptions.ExpireTimeSpan;
options.SlidingExpiration = cookieAuthenticationOptions.SlidingExpiration;
});
Perhaps not the best way but then I can still use Services.ConfigureApplicationCookie options plus be able to override SecurityStampValidatorOptions and part of CookieAuthenticationOptions from settings (appsettings.json, ENV etc) and by that I can deploy the application with different settings.
I couldn't find out how to configure the application cookie directly
from the configuration section so a selected few attributes are set
if the setting are available.
So it didn't solve my real question but this is good enough because I've different deploy options and locations so it is a working walk-around.
I can then for exempel do like this in appsettings.json:
"SecurityStampValidatorOptions": {
"ValidationInterval": "0.00:00:20"
},
"CookieAuthenticationOptions": {
"SlidingExpiration": true,
"ExpireTimeSpan": "0.02:00:00"
}
and override the default values and also on the upfront is that there are no hard-coded values in Startup.