ASP.NET Core website timing out after 30 minutes - c#

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.

Are you hosted under IIS? If so, maybe nothing is wrong with your code, but your application pool could get recycled (check advanced settings on the application pool). When that happen, does your binary get unloaded from memory and replaced by a new one, its PID changing ?

Related

Can't call controller method on dotnet core api: no middleware to handle the request

I'm facing an issue with my dotnet core api with angular.
I simply made a login form and a UserController to handle login requests with jwt but whenever i hit the "Login" button and call the UserController's Login method from my angular app with this:
this.http.post<any>(environment.baseUrl + `api/user/login`, user)
I always get the following message:
"The SPA default page middleware could not return the default page '/index.html' because it was not found, and no other middleware handled the request."
I'm looking for more than a solution on this one because i've been searching to fix it the whole day and i couldn't figure out how i could solve it. I looked at iis logs, eventviewer logs. I tried debugging and did a fair amount of internet research, but I can't find a proper way to actually handle this kind of error
Since the error is raised by the dotnet core framework, i don't really know how to determine if it's my controller that is broken or if i made a mistake while configuring services.
How do you usually figure this out?
Thanks for your time and your help in advance ;)
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var ConnectionString = Configuration.GetConnectionString("Default");
services.AddDbContext<TheCompanyContext>(options => options.UseSqlServer(ConnectionString));
services.AddControllers();
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = $"ClientApp/dist";
});
services.ConfigureCORS();
var key = Encoding.ASCII.GetBytes(Configuration["AppSettings:AuthenticationSecret"]);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
app.UseHttpsRedirection();
}
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
UserController.cs:
[Route("api/[controller]/[action]")]
[ApiController]
public class UserController : Controller
{
public UserController(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
[HttpPost]
[AllowAnonymous]
public ActionResult Login([FromBody]User user)
{
// do login stuff
}
}
I restarted a project from scratch and managed to solve the issue.
I don't really don't know what solved the issue but here's the 3 main thing i did:
Rework CORS policy
Rework environment settings for both angular and dotnet
Solved a database connection issue
My guess is that my environment settings where kind of badly made and i was requesting the wrong api url and CORS blocked me. All of which was hiding my database issue.
Sorry i don't have a proper fix for this, but since i wasted so much time on this, restarting was easier ^^
Thanks anyway
For me the problem was "AllowedHosts": "*" in appsettings.development.json!
You can try to setting environment variable ASPNETCORE_Environment=Development.
And you can also try to change "ouputPath" in angular.json . Normally it seems to be "dist/{ProjectName}". Change this to just "dist".
More details, you can refer to this discussion.

What would be the authorization workflow for multiple applications with the same authentication web API?

I have two applications, one written in VB.Net using Asp.Net Web Forms (app1). The second is in C# using Asp.Net Core MVC (app2). I want to create a web API that authenticates a user trying to access either app and shares that authorization token between the apps. If the user goes from app1 to app2, the JWT token will be deemed valid and that user will effectively be logged on. If the same user signs out at any point, it removes the JWT token and will require a login again.
I have built a web api that runs with identity and entity framework that already creates JWT tokens if you have an account in identity and successfully performs authorization in itself. I am struggling with getting app2 to accept that JWT and somehow dissect it to see what roles the user has in app2. Here is my current Startup.cs page with how I've wired the JwtBearer:
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)
{
var authManager = Configuration.GetSection("AuthenticationManager");
var key = TextEncodings.Base64Url.Decode(authManager.GetSection("AudienceSecret").Value);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.Authority = authManager.GetSection("Address").Value;
options.Audience = "my secret audience";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false
};
});
services.AddControllersWithViews();
services.AddMvc();
}
// 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.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Is there a smart way to redirect for login and have app2's controller actions have my [Authorize] attribute just look at the JWT from the API?
Thanks!
You may want to consider something like IdentityServer for this. It can handle these sorts of scenarios out of box -- it's a pretty well-proven .Net solution with extensive documentation and sample code. https://identityserver.io/

React App should authenticate against ASP.Net Core API Project via MSAL

I am a complete newbie to React and MSAL. I created an ASP.Net Core MVC WebApplication which connects to a Database in Azure. It uses .Net Core 3 and Identity UI. Now I have to create a react webapplication which uses the API of my MVC WebApplication.
I have a requirement to use our existing Azure AD to authenticate and to create a React Webapplication as an User Interface. My problem is where and how to start. The React Application should do the login process. The roles of the users should be managed in the existing database. Before the requirement the MVC Application handled the registration and login via identity UI and stored users and roles in the azure database. Now the users should authenticate via azure ad.
As far as I understand msal, the react application should redirect to the azure ad login and will aquire a token and the azure service will after successful login redirect back to the react app.
How does the react app give the token to my MVC Webapplication? How do i have to change the controllers that a token is passed and validated? do i have to setup some things for MSAL or authentication in the startup.cs?
I created a controller for testing purposes:
public class TestAPIController : Controller
{
private readonly IBaseRepository<Articles> _BaseRepository;
public TestAPIController(IBaseRepository<Articles> _BaseRepository)
{
this._BaseRepository = _BaseRepository;
}
// GET
[HttpGet]
[Route("GetArticles")]
public async Task<IActionResult> GetArticles()
{
return Ok(JsonConvert.SerializeObject(await _BaseRepository.GetAllAsync()));
}
}
My startup.cs looks like this:
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.AddDbContext<BaseContext>(opts => opts.UseSqlServer(Configuration.GetConnectionString("My_ConnectionString")));
//CookiePolice
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
options.Stores.MaxLengthForKeys = 128;
})
.AddEntityFrameworkStores<BaseContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
options.Cookie.Name = Common.Common_Ressources.LoginCookieName;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout");
});
}
// 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");
app.UseHsts();
}
app.UseRouting();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseCookiePolicy();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllers();
endpoints.MapRazorPages();
});
}
}
Is there a small piece of code that will show me how the react app should aquire the token and pass it to the MVC Application? Could you please provide some details that i have to know before implementing this? I hope i can find the best way to achieve the goal of the requirement. Every little piece of code could help me understand it a little better.
Thanks in advance
There are two main steps.
1.Protect your webapi(server) with azure ad. You can refer to this sample(TodoListService).
2.Get access token from your react app(client), then access webapi with the token.
Here is a complete video tutorial and source code on how to use MSAL with React to call Microsoft Graph.
The only different in your case will be that instead of calling Microsoft Graph, you will call your own API.

Issue with Identity when switching to the Create view

So I was following this tutorial: https://www.yogihosting.com/aspnet-core-identity-create-read-update-delete-users/ teaching how to make a CRUD for Identity users.
I've reached a point where I am getting 2 errors which I think are tied together. Firstly, I will present the code:
AdminController.cs
public ViewResult Create() => View();
[HttpPost]
public async Task<IActionResult> Create(User user)
{
if (ModelState.IsValid)
{
AppUser appUser = new AppUser
{
UserName = user.Name,
Email = user.Email
};
IdentityResult result = await userManager.CreateAsync(appUser, user.Password);
if (result.Succeeded)
return RedirectToAction("Index");
else
{
foreach (IdentityError error in result.Errors)
ModelState.AddModelError("", error.Description);
}
}
return View(user);
}
AppUser.cs
public class AppUser : IdentityUser
{
}
When accessing the `localhost/Admin/Create' link after running the application, I am getting this error:
InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[Intersection.Models.AppUser]' while attempting to activate 'Filters.Controllers.AdminController'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
Then, I figured that something might be wrong in the Startup.cs, therefore, after a bit of research, I added this line: services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();
In an attempt to fix the first error, I got this second issue:
HTTP Error 500.30 - ANCM In-Process Start Failure
My Startup.cs class looks like this:
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.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
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.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
So what could be the issue? I tried following the tutorial closely but apparently I missed something out... Or things changed since it was posted. Any help is highly appreciated!
HTTP Error 500.30 - ANCM In-Process Start Failure
This was due to methods AddDbContext and AddIdentity having duplicates in the Startup.cs. After commenting out the duplicates, I got rid of it.
Secondly, in _LoginPartial.cshtml, I had this:
#inject SignInManager<IdentityUser> SignInManager
#inject UserManager<IdentityUser> UserManager
I had to replace IdentityUser with my AppUser. This fixed the first error.

.NET Core 2.0 accessing user secrets

I'm trying to setup a .net core 2.0 web application to send an email when user registers and also to recover password. I have followed this tutorial: https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?tabs=visual-studio.
However, upon reading the comments sections, it seems that the tutorial is not updated for Core 2.0. My question is, when I get to part "Add the user secrets configuration source to the Startup method", I cannot figure out how the startup file should look like since my startup file is different from the one showed there. Can anyone help me by showing me how the startup file should look like? Thanks.
This is my current startup file:
public class Startup
{
string _testSecret = null;
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)
{
_testSecret = Configuration["MySecret"];
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
services.Configure<AuthMessageSenderOptions>(Configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var builder = new ConfigurationBuilder();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseDatabaseErrorPage();
builder.AddUserSecrets<Startup>();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
In your code I didn't find invoking of build() function of ConfigurationBuilder class. Here is the sample code.
var builder = new ConfigurationBuilder();
builder.AddUserSecrets<Startup>();
var config builder.Build(); //This line is missing from your code
string mySecret = config ['EmailAccount'];
Refernce: https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-2.1&tabs=windows#access-a-secret

Categories

Resources