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.
Related
My project uses role-based authorization and it has over 100 roles. I've noticed that before every action, server queries each user role and it's claims separately. That's over 200 queries before each action. Even an empty controller does this, so I assume this is ASP.NET Identity Core functionality. Is there any way to optimize this?
Thanks in advance.
ASP.NET Core web server output (one out of many role queries):
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[#__role_Id_0='390'], CommandType='Text', CommandTimeout='30']
SELECT [rc].[ClaimType], [rc].[ClaimValue]
FROM [AspNetRoleClaims] AS [rc]
WHERE [rc].[RoleId] = #__role_Id_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[#__normalizedName_0='100' (Size = 256)], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [r].[Id], [r].[ConcurrencyStamp], [r].[Name], [r].[NormalizedName]
FROM [AspNetRoles] AS [r]
WHERE [r].[NormalizedName] = #__normalizedName_0
My Startup.cs class:
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.AddRouting(options => options.LowercaseUrls = true);
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromDays(1);
options.Cookie.IsEssential = true;
});
services.AddDbContext<AppDbContext>(options =>
options
.EnableSensitiveDataLogging()
.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), x =>
{
x.UseRowNumberForPaging();
x.UseNetTopologySuite();
}));
services.Configure<WebEncoderOptions>(options =>
{
options.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.All);
});
services.Configure<AppConfiguration>(
Configuration.GetSection("AppConfiguration"));
services.AddIdentity<User, UserRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
services.Configure<SecurityStampValidatorOptions>(options =>
{
// enables immediate logout, after updating the users stat.
options.ValidationInterval = TimeSpan.Zero;
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.Cookie.Expiration = TimeSpan.FromDays(150);
// If the LoginPath isn't set, ASP.NET Core defaults
// the path to /Account/Login.
options.LoginPath = "/Account/Login";
// If the AccessDeniedPath isn't set, ASP.NET Core defaults
// the path to /Account/AccessDenied.
options.AccessDeniedPath = "/Account/AccessDenied";
options.SlidingExpiration = true;
});
// Add application services.
services.AddScoped<IEmailSenderService, EmailSenderService>();
services.AddScoped<IUploaderService, UploaderService>();
services.AddScoped<IPdfService, PdfService>();
services.AddScoped<ICurrencyRateService, CurrencyRateService>();
services.AddScoped<IViewRenderService, ViewRenderService>();
services.AddScoped<IUserCultureInfoService, UserCultureInfoService>();
services.AddScoped<IUserService, UserService>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
services
.AddMvc(options =>
{
options.EnableEndpointRouting = false;
options
.RegisterDateTimeProvider(services)
.ModelMetadataDetailsProviders
.Add(new BindingSourceMetadataProvider(typeof(ListFilterViewModel), BindingSource.ModelBinding));
})
.AddSessionStateTempDataProvider()
.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();
// app.UseMiddleware<StackifyMiddleware.RequestTracerMiddleware>();
}
else
{
#if DEBUG
app.UseDeveloperExceptionPage();
#else
app.UseExceptionHandler("/Default/Error");
#endif
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSession();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapAreaRoute(
name: "Hubs",
areaName:"Hubs",
template: "Hubs/{controller=CompanyAddresses}/{action=Index}/{id?}");
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Default}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "default",
template: "{controller=Default}/{action=Index}/{id?}");
});
}
}
I have found out what causes this weird behavior. It's this code segment in my Startup.cs class:
services.Configure<SecurityStampValidatorOptions>(options =>
{
// enables immediate logout, after updating the users stat.
options.ValidationInterval = TimeSpan.Zero;
});
Removing it solved my problem. I've been using this to force logout users by updating their security stamp as described here: How to sign out other user in ASP.NET Core Identity
It seems I will have to look for other solution for force logout, but I'm happy that requests are now not generating hundreds of SQL queries.
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.
I encounter this error in my API IDX10214: Audience validation failed. Audiences: 'https://localhost:44337/resources'. Did not match: validationParameters.ValidAudience: 'joborderingapi' or validationParameters.ValidAudiences: 'null'
I've been trying to solve this for 2 days already and can't figure out yet about how to solve it.
I have the following applications:
Client App (Angular 7)
Identity Server
API
I was able to login successfully to Identity Server in my Client app and was able to get the token but when I used the token to connect to the API method it throws this error IDX10214: Audience validation failed. Audiences: 'https://localhost:44337/resources'. Did not match: validationParameters.ValidAudience: 'joborderingapi' or validationParameters.ValidAudiences: 'null'.
I followed the answer from Identity Server 4 with EF Identity DB Issue and checked the three tables (ApiResources, ApiScopes, ClientScopes), the values are correct, joborderingapiis enabled in ApiResources, in ApiScopes it is linked to ApiResource and in ClientScopes it is linked to the Client
Here is my API Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var apiIdpAuthority = Configuration["AppSettings:IdpAuthority"];
var apiName = Configuration["AppSettings:ApiName"];
var apiSecret = Configuration["AppSettings:ApiSecret"];
var requireHttps = Convert.ToBoolean(Configuration["AppSettings:RequireHttps"]);
var httpsPort = Configuration["AppSettings:HttpsPort"];
var applicationUrl = Configuration["AppSettings:ApplicationUrl"];
services.Configure<ClientAppSettings>(Configuration.GetSection("ClientAppSettings"));
services.AddDbContext<JobOrderingDataContext>(options => options.UseSqlServer(Configuration.GetConnectionString("JobOrderingDB")));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
IdentityModelEventSource.ShowPII = true;
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = apiIdpAuthority;
options.RequireHttpsMetadata = requireHttps;
options.ApiName = apiName;
options.ApiSecret = apiSecret;
});
services.AddCors(options =>
{
// this defines a CORS policy called "default"
options.AddPolicy("default", policy =>
{
policy.WithOrigins(apiIdpAuthority, applicationUrl)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()
.AllowCredentials();
});
});
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(60);
});
services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
options.HttpsPort = Convert.ToInt32(httpsPort);
});
}
// 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();
}
else
{
app.UseHsts();
}
app.UseCors("default");
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseHttpsRedirection();
app.UseCookiePolicy();
var locale = Configuration["SiteLocale"];
var supportedCultures = new List<CultureInfo> { new CultureInfo("en-US") };
if (supportedCultures.Where(x => x.Name == locale).Count() == 0)
{
supportedCultures.Add(new CultureInfo(locale));
}
RequestLocalizationOptions localizationOptions = new RequestLocalizationOptions()
{
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures,
DefaultRequestCulture = new RequestCulture(locale)
};
app.UseRequestLocalization(localizationOptions);
app.UseAuthentication();
app.UseMvc();
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");
}
});
}
Identity Server Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//var microsoftClientId = Configuration["MicrosoftClientId"];
// var microsoftClientSecret = Configuration["MircosoftClientSecret"];
var azureADClientId = Configuration["AzureADClientId"];
var azureADClientSecret = Configuration["AzureADClientSecret"];
var azureADEndPoint = Configuration["AzureADEndPoint"];
var issuerUri = Configuration["IssuerUri"];
string connectionString = Configuration.GetConnectionString("DefaultConnection");
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddDbContext<IdentityServerDataContext>(options => options.UseSqlServer(connectionString));
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication()
// .AddCookie()
.AddOpenIdConnect("AAD", "Azure AD", options =>
{
options.Authority = string.Format("https://login.microsoftonline.com/{0}", azureADEndPoint);
options.ClientId = azureADClientId;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
});
IdentityModelEventSource.ShowPII = true;
services.AddTransient<IProfileService, IdentityWithAdditionalClaimsProfileService>();
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddIdentityServer() .AddSigninCredentialFromConfig(Configuration.GetSection("SigninKeyCredentials"), _logger)
// this adds the config data from DB (clients, resources)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
// this adds the operational data from DB (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 30;
})
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<IdentityWithAdditionalClaimsProfileService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// this will do the initial DB population
// InitializeDatabase(app);
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentityServer();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Note: I only encounter this issue when I use the local login. It is working fine if I use the Azure AD login, I was able to connect to the API using the authorization token from the client app
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>();
};