I have an ASP.NET Core 2.1 MVC application in which I have configured OpenIdConnect provider for authentication. The Startup class looks like below:
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 => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(1200);
options.Cookie.HttpOnly = true;
});
services.AddHttpContextAccessor();
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IClientDataHandler, ClientDataHandler>();
services.AddAuthentication(options => .AddOpenIdConnect("oidc", options =>
{
...
options.Events.OnTokenValidated = async x =>
{
var serviceScopeFactory = services.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>();
...
await x.HttpContext.Session.LoadAsync(new CancellationToken()); --does NOT work
x.HttpContext.Session.Set("clients", Utils.ObjectToByteArray(someData)); --does NOT work
};}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSession();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Though this lets me use HttpContext.Session (by injecting IHttpContextAccessor) in any controller or service, I can't use the Session in TokenValidated event handler. Any help?
Thanks in advance.
You should not be building the service provider in your event handler. This is not executed during startup. It's executed on each request by your authentication handler long after the service provider has been built.
options.Events.OnTokenValidated = async context =>
{
// don't do this...service provider is already built
var serviceScopeFactory = services.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>();
};
Instead, you can access the built service provider from the HttpContext.RequestServices.
options.Events.OnTokenValidated = async context =>
{
var serviceScopeFactory = context.HttpContext.RequestServices.GetRequiredService<IServiceScopeFactory>();
};
Related
I'm working with .NetCore API and MVC. Everything works on the local server. But the session does not occur after publishing the site.
The operation of the project is as follows:
User come with token ID to website.
I'm parsing it and then saving important variables to session.
I'm getting values from session.
...
As you can see If I get error on session other steps will fail.
I tried too many ways but none of them worked.
I'm adding Startup.cs/ConfigureServices and Configure methods.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages().AddRazorRuntimeCompilation();
services.AddMvc().AddControllersAsServices();
services.AddWkhtmltopdf("wkhtmltopdf");
services.AddHttpContextAccessor();
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.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(600);//You can set Time
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
services.AddAuthentication();
services.AddScoped<Business.Business, Business.Business>();
services.AddSingleton<SharedViewLocalizer>();
#region Localization and Language
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.Configure<RequestLocalizationOptions>(options =>
{
var cultures = new[] {
new CultureInfo("tr-TR"),
new CultureInfo("en-US"),
new CultureInfo("de-DE"),
};
options.DefaultRequestCulture = new RequestCulture("en-US");
options.SupportedCultures = cultures;
options.SupportedUICultures = cultures;
});
#endregion
services.AddMvc(option =>
{
option.EnableEndpointRouting = false;
})
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix,
opts => { opts.ResourcesPath = "Resources"; })
.AddDataAnnotationsLocalization();
services.AddRazorPages();
services.AddServerSideBlazor();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseDeveloperExceptionPage();
//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();
}
#region Language Options
var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
var cookieProvider = options.Value.RequestCultureProviders
.OfType<CookieRequestCultureProvider>()
.First();
var urlProvider = options.Value.RequestCultureProviders
.OfType<QueryStringRequestCultureProvider>().First();
cookieProvider.Options.DefaultRequestCulture = new RequestCulture("tr-TR");
urlProvider.Options.DefaultRequestCulture = new RequestCulture("tr-TR");
cookieProvider.CookieName = CookieRequestCultureProvider.DefaultCookieName;
options.Value.RequestCultureProviders.Clear();
options.Value.RequestCultureProviders.Add(cookieProvider);
options.Value.RequestCultureProviders.Add(urlProvider);
app.UseRequestLocalization(options.Value);
#endregion
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseSession();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
#region Routing
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
"areas",
"{area:exists}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Login}/{id?}");
endpoints.MapRazorPages();
//endpoints.MapBlazorHub();
//endpoints.MapFallbackToController("Index", "Home");
});
#endregion
}
options.Cookie.SameSite = SameSiteMode.None must be used to allow cross-site cookie use.
https://learn.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-5.0
The order of middleware is important. Call UseSession after UseRouting and before UseEndpoints. See Middleware Ordering.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-5.0
If your production environment consists of multiple servers, the easy way out (but not the recommended way), is to enable sticky sessions in your load balancer.
Using distributed cache is the recommended way when using multiple servers. More info: https://dzone.com/articles/aspnet-core-session-storage-strategies
You should consider not to use sessions at all, that will make things a lot easier for you.
I have vue.js frontend and asp.net core 3.1 backend. Backend uses SignInManager and Identity. I am trying to use cookie authentication. Api requests work from Postman(!!) roles are applied, everything, but do not from vue app (httpContext.User.Identity.IsAuthenticated is false). Indentity is empty. Cookie is present in the HttpContext
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDBContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:Default"]));
services.AddCors();
services.AddControllers()
services.AddIdentity<AppUser, IdentityRole>(
opts =>
{
opts.SignIn.RequireConfirmedEmail = true;
}
)
.AddSignInManager<SignInManager<AppUser>>()
.AddEntityFrameworkStores<MyDBContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.Cookie.SameSite = SameSiteMode.None;
options.SlidingExpiration = true;
});
//some DI
...
//
}
bit more Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, UserManager<AppUser> userManager)
{
app.UseRouting();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
I was trying to proxy requests to api in vue app. Did not help
module.exports = {
devServer: {
proxy: {
'^/api': {
target: 'https://localhost:44376',
ws: true,
changeOrigin: true
}
}
}
}
What can be wrong?
Maybe you are missing AddRoles<IdentityRoles>()?
services.AddIdentity<AppUser, IdentityRole>(
opts =>
{
opts.SignIn.RequireConfirmedEmail = true;
}
)
.AddRoles<IdentityRoles>() .// <== this line
.AddSignInManager<SignInManager<AppUser>>()
.AddEntityFrameworkStores<MyDBContext>()
.AddDefaultTokenProviders();
After working on my project for a while, I released the HttpContext.User.IsAuthenticated() returns False after login and I need to know where I should look for the mistake I made that cause this problem.
This is the Login, OnPost method.
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = _userManager.Users.FirstOrDefault(u => u.StudentNumber == Input.StudentNumber.ToString());
if (!(user is null) && await _userManager.CheckPasswordAsync(user, Input.Password))
await _signInManager.SignInAsync(user, Input.RememberMe);
var isUserAuthenticated = HttpContext.User.IsAuthenticated();
return Redirect(returnUrl);
}
// If we got this far, something failed, redisplay form
return Page();
}
The ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>(option=>option.Password.RequireNonAlphanumeric=false)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddMvc(options => options.EnableEndpointRouting = false)
.AddNewtonsoftJson();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
The Configure method.
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();
app.UseSpaStaticFiles();
app.UseAuthentication();
app.UseIdentityServer();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
SignInManager.SignInAsync() only creates the cookie for the given user. This method would not set HttpContext.User.
But in the next request which has the cookie you can access HttpContext.User after AuthenticationMiddleware and HttpContext.User.IsAuthenticated() should be true.
AuthenticationMiddleware always try to authenticate user with the default scheme and since you have AddIdentityServer after AddDefaultIdentity, identity server is becoming your default scheme, but when you call SignInManager.SignInAsync the Identity scheme is triggered.
To sum up, with this configuration your AuthenticationMiddleware always tries to authenticate request for IdentityServer and if you want other scheme for you apis you should use [Authorize(AuthenticationSchemes = "Identity.Application")].
P.S. Identity.Application is authenticatio scheme for ASP.NET Identity
HttpContext.User.Claims and IHttpContextAccessor both returns empty value after successful login in .NET Core 2.2
Here my Startup Services,
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
,b=>b.MigrationsAssembly("AdaptiveBizapp")));
services.AddDbContext<Project_Cost_Management_SystemContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Project_Cost_Management_SystemContext")
, b => b.MigrationsAssembly("AdaptiveBizapp")));
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.ConfigureApplicationCookie(options => {
options.LoginPath = "/Account/login";
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly = true;
// Make the session cookie essential
options.Cookie.IsEssential = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
and my Configure section,
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSession();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "area",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
});
I used Identity and Role based authorization. After login successful, in HomeController when i read user claims or NameIdentifier is empty. But when i read in same LoginController It has value at ClaimPrincipal,
public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal =await base.CreateAsync(user);
// Add your claims here
((ClaimsIdentity)principal.Identity).
AddClaims(new[] {
new System.Security.Claims.Claim(ClaimTypes.NameIdentifier,
user.UserName.ToString())
});
return principal;
}
Searched all day and finally figured it out. The claims are hydrated on the next call to the server. Hopefully this post helps someone else out. If you follow microsofts documentation you are good but just dont try to get the claims in the same call as when you set them. Do another call and they will be hydrated.
if you want use Depency Injection for IHttpContextAccessor you need to add :
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
...
services.AddHttpContextAccessor();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
...
}
}
Having scaffolded the Identity Area in Aspnet Core 2.1. with this startup class:
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)
{
// AppSettings.json
//
services.Configure<AppSettings>(Configuration.GetSection("ProvidersSettings"));
// IdentityUser settings
//
services.Configure<IdentityOptions>(options =>
{
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+";
options.User.RequireUniqueEmail = true;
});
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc(options =>
{
// All MvcControllers are hereby "Authorize". Use AllowAnonymous to give anon access
//
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
options.Filters.Add(new AuthorizeFilter(policy));
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
// IdentityModel
//
services.AddSingleton<IDiscoveryCache>(r =>
{
var url = Configuration["ProvidersSettings:IdentityServerEndpoint"];
var factory = r.GetRequiredService<IHttpClientFactory>();
return new DiscoveryCache(url, () => factory.CreateClient());
});
// HttpContext injection
//
services.AddHttpContextAccessor();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, UserManager<IdentityUser> userManager, ApplicationDbContext dbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
It works in development. But in production it doesn't stay on https. It redirects to http and this path:
/Identity/Account/Login?ReturnUrl=%2F
On localhost it works correctly with both IIS express and "program".
Any ideas are most helpful. Thanks.