Razor Session Data - HttpContextAccessor is null - c#

I am a MVC5 newbie, creating a test web Site to look at MVC5 and Razor
I have a very simple site after user logs on and I need to change menus via _Layout from "Login" to add "Logout" "Account".
Note: The site is has its own authentication, I will look at single login later.
I am really struggling with managing session data in MVC5, not sure which is the best approach. I have tried TEMP DATA , but although I peak I have found that after user has been redirected between a couple of pages the data is lost. So looked at good old cookie, but since GDPR I can tell there is a lot less default support for cookies out of the box.
Anyway in the Startup I believe I am doing all the right things
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set session timeout value
options.IdleTimeout = TimeSpan.FromSeconds(30);
options.Cookie.HttpOnly = true;
});
}
services.AddMvc();
services.AddCaching();
services.AddSession();
services.AddHttpContextAccessor();
dependency inject it in , but in the Post when I attempt to call the SetString , the "private readonly IHttpContextAccessor _httpContextAccessor;" is null
Oddly the _Layout is not throwing same null exception
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor HttpContextAccessor
#{
string UserId = HttpContextAccessor.HttpContext.Session.GetString("UserId");
}
After working on ASP.Net , the simple approach to handling Session , beginning to question whether I have missed something as it seems a lot of work in MVC5. So should I be using a different approach in MVC5

Just want to add this this for anyone else , probably not the cleanest code
With help form Sanjay now got a crude site up and running
Startup class , example code
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddHttpContextAccessor();
services.AddRazorPages();
services.AddRazorPages();
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(30);
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
}
Code for .csHtml
#using Microsoft.AspNetCore.Http;
#inject IHttpContextAccessor HttpContextAccessor
#{
if (Context.Session.GetString("UserRole") != null)
{
code for .cs
private readonly IHttpContextAccessor HttpContextAccessor;
public LoginModel(ISiteUserService siteUserService,
IHttpContextAccessor httpContextAccessor)
{
this.HttpContextAccessor = httpContextAccessor;
}
public IActionResult OnGet()
{
if(HttpContextAccessor.HttpContext.Session.GetString("UserRole")!= null)
{

Set
options.CheckConsentNeeded = context => false;
this to true i.e
options.CheckConsentNeeded = context => true;
and your session will not be null anymore.

Related

Blazor Server and CleanCode - ASP.NET Core Identity

I am having trouble wiring up identity into Blazor server with ASP.NET Core identity. Specifically getting the correct logged in state in Blazor pages (while I am getting them from the Blazor pages).
I think it's related to some of the startup being initialized in another project - but not sure how to debug it or what the solution is to be able to get the logged in state correctly.
Reproduction steps and link to GH repo below as a POC.
Background
I'm porting over the clean-code project by JasonTaylor from Angular / ASP.NET Core to a Blazor server project with ASP.NET Core Identity.
Issue
The application runs up and I can browse the pages when I register I can see logged-in state in the identity-based default pages but in the Blazor pages that use the AuthorizeView (e.g. LoginDisplay.razor) it's not aware of being authorized.
Startup in the Blazor project:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddApplication();
services.AddInfrastructure(Configuration);
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSingleton<ICurrentUserService, CurrentUserService>();
services.AddHttpContextAccessor();
services.AddHealthChecks()
.AddDbContextCheck<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
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.UseHealthChecks("/health");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
AddInfrastructure in another project references in startup:
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
if (configuration.GetValue<bool>("UseInMemoryDatabase"))
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase("CleanArchitectureDb"));
}
else
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
}
services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
services.AddScoped<IDomainEventService, DomainEventService>();
services
.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<ApplicationUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddTransient<IDateTime, DateTimeService>();
services.AddTransient<IIdentityService, IdentityService>();
services.AddTransient<ICsvFileBuilder, CsvFileBuilder>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddAuthorization(options =>
{
options.AddPolicy("CanPurge", policy => policy.RequireRole("Administrator"));
});
return services;
}
}
public class RevalidatingIdentityAuthenticationStateProvider<TUser>
: RevalidatingServerAuthenticationStateProvider where TUser : class
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IdentityOptions _options;
public RevalidatingIdentityAuthenticationStateProvider(
ILoggerFactory loggerFactory,
IServiceScopeFactory scopeFactory,
IOptions<IdentityOptions> optionsAccessor)
: base(loggerFactory)
{
_scopeFactory = scopeFactory;
_options = optionsAccessor.Value;
}
protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30);
protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken)
{
// Get the user manager from a new scope to ensure it fetches fresh data
var scope = _scopeFactory.CreateScope();
try
{
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>();
return await ValidateSecurityStampAsync(userManager, authenticationState.User);
}
finally
{
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}
private async Task<bool> ValidateSecurityStampAsync(UserManager<TUser> userManager, ClaimsPrincipal principal)
{
var user = await userManager.GetUserAsync(principal);
if (user == null)
{
return false;
}
else if (!userManager.SupportsUserSecurityStamp)
{
return true;
}
else
{
var principalStamp = principal.FindFirstValue(_options.ClaimsIdentity.SecurityStampClaimType);
var userStamp = await userManager.GetSecurityStampAsync(user);
return principalStamp == userStamp;
}
}
}
Steps to reproduce
Register : https://localhost:44399/Identity/Account/Register
Browse to : https://localhost:44399/Identity/Account/Login - Notice username in header is populated from the ASP.Net Identity pages
Browse to : https://localhost:44399/ - Notice the Header is Register, Login, About (Based on https://github.com/davidshorter/CleanCodeBlazor/blob/Rework/src/Web/Shared/LoginDisplay.razor)
Pushed up my changes to GH if anyone fancies a
look : https://github.com/davidshorter/CleanCodeBlazor/tree/Rework
This was an issue with mixing IdentityServer and ASP.net identity.
By removing Microsoft.AspNetCore.ApiAuthorization.IdentityServer and the use of base class from ApiAuthorizationDbContext<ApplicationUser> back to IdentityDbContext<ApplicationUser> resolved this.

how to solve the loop in IdentityServer4 - connect/authorize/callback?client_id=?

hello community I have a project created with asp.net core, blazor webassembly and Identity4, the local project works very well, I published it on an IIS server so that it could be seen from the internet, the project was loaded perfectly, the only detail is that when I enter the login it is loading, until I give it to empty cache and load in a forced way, I can enter the login form, then I enter my credentials and again it stays loading until I empty the cache again and enter the page that indicates I'm already logged in.
How can I enter the login form without emptying the cache and loading
forcefully?
When I click the login button, it sends me to this route but it's wrong:
connect/authorize?client_id=BlazorApp.Client&redirect_uri=https%3A%2F%2
this is the route that is fine to take me:
Identity/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id
this is my class startup:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SimulexContext>
(options => options.UseSqlServer(Configuration.GetConnectionString("SimulexConnection")));
services.AddDefaultIdentity<ApplicationUser>(options => {
options.SignIn.RequireConfirmedAccount = true;
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
.AddProfileService<IdentityProfileService>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.Configure<PayPalConfiguration>(Configuration.GetSection("PayPal"));
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAutoMapper(typeof(Startup));
services.AddScoped<NotificacionesService>();
services.AddScoped<IAlmacenadorDeArchivos, AlmacenadorArchivosLocal>();
services.AddHttpContextAccessor();
services.AddMvc().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
}
// 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();
app.UseWebAssemblyDebugging();
}
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.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/api/config/notificacionesllavepublica", async context =>
{
var configuration = context.RequestServices.GetRequiredService<IConfiguration>();
var llavePublica = configuration.GetValue<string>("notificaciones:llave_publica");
await context.Response.WriteAsync(llavePublica);
});
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
}
this is my page appsettingsjson:
"IdentityServer": {
"Key": {
"Type": "Development"
},
"Clients": {
"BlazorApp.Client": {
"Profile": "IdentityServerSPA"
}
}
},
Can you share your startup for the blazor app?
I had the same issue a couple weeks ago but with an asp.net mvc app when integrating is4 with which had identity configured. So it could be same for you.
Enet's solution might work, but I have not tried it. Below is another solution which worked for me only if you have Identity configured in your Blazor app. Try setting the schemes in your services.AddAuthentication and AddOpenIdConnect:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "Oidc";
options.DefaultAuthenticateScheme = "Cookies"; // <-- add this line
})
.AddCookie("Cookies", options =>
{
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies"; // <-- add this line
})
;

Cookie not expiring per StartUp.cs settings

I have tried a plethora of solutions over the course of 2 days and still have not been able to get this to work. What I want is for a user cookie to expire after a set amount of time
E.g. User A logs in and goes to home page, User A goes for a lunch break. User A comes back and clicks on the nav bar and gets redirected to the login page.
I have tried everything from AddAuthentication(), AddSession() and AddCookie() options all having an ExpireTimeSpan and Cookie.Expiration of my choosing. Nothing seems to work. The project uses ASP.NET Identity and I am aware this service should be called before the cookie options. Please see my current StartUp.cs below, this is the last thing i tried:
Startup.cs
public class Startup
{
public IConfiguration Configuration { get; }
public IContainer ApplicationContainer { get; private set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
//other services e.g. interfaces etc.
services.AddAuthentication().AddCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.Expiration = TimeSpan.FromSeconds(60);
options.LoginPath = "/Account/Login";
options.LogoutPath = "/Account/Logout";
options.AccessDeniedPath = "/AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromSeconds(5);
options.SlidingExpiration = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//services.AddSession();
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(services);
this.ApplicationContainer = containerBuilder.Build();
var serviceProvider = new AutofacServiceProvider(this.ApplicationContainer);
return serviceProvider;
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.ConfigureCustomExceptionMiddleware();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
//app.UseSession();
app.UseMvc();
}
}
The following code isn't affecting the Identity cookie:
services.AddAuthentication().AddCookie(options => ...);
Instead, it's adding a new cookie-based authentication scheme, named Cookies, and configuring that. With all the standard Identity setup, this scheme is unused, so any changes to its configuration will have no effect.
The primary authentication scheme used by Identity is named Identity.Application and is registered inside of the AddIdentity<TUser, TRole> method in your example. This can be configured using ConfigureApplicationCookie. Here's an example:
services.ConfigureApplicationCookie(options => ...);
With that in place, the cookie options will be affected as intended, but in order to set a cookie with a non-session lifetime, you also need to set isPersistent to true inside your call to PasswordSignInAsync. Here's an example:
await signInManager.PasswordSignInAsync(
someUser, somePassword, isPersistent: true, lockoutOnFailure: someBool);

Asp.Net Core | Extend windows auth identity object

I want to use Windows Auth in my intranet application, but I need to extend the identity object to get some extra data. As of now, I only have access to the domain name in the identity user. I tried to implement my own user/role store in order to intercept the authorization calls then use the domain name to go to our database and grab the extra data. I implemented my own store, but none of the methods seem to be called. How do I intercept when the app authorized the window user so that I can go to our database and grab what I need to put in the user object?
Here's my Startup.cs
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.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddIdentity<MyUser, IdentityRole>()
.AddUserStore<MyUserStore>()
.AddRoleStore<MyRoleStore>()
.AddDefaultTokenProviders();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
What I did was deleting the basic authentication from MVC and added my AuthenticationHandler which extends the AuthenticationService because I don't want to reinvent every method from IAuthenticationService so:
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.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddIdentity<MyUser, IdentityRole>()
.AddUserStore<MyUserStore>()
.AddRoleStore<MyRoleStore>()
.AddDefaultTokenProviders();
services.Remove(services.FirstOrDefault(x => x.ServiceType == typeof(IAuthenticationService)));
services.Add(new ServiceDescriptor(typeof(IAuthenticationService),typeof(AuthenticationHandler), ServiceLifetime.Scoped));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
And then
public class AuthenticationHandler : AuthenticationService
{
private readonly ILdapRepository _ldapRepository;
public AuthenticationHandler(ILdapRepository ldapRepository,
IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers,
IClaimsTransformation transform) : base(schemes, handlers, transform)
{
_ldapRepository = ldapRepository;
}
public async override Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme)
{
var idk = await base.AuthenticateAsync(context, scheme);
if (idk.Succeeded) {
var claims = _ldapRepository.LoadClaimsFromActiveDirectory(idk.Principal.Claims.FirstOrDefault(x => x.Type == CustomClaimTypes.Name)?.Value);
idk.Principal.AddIdentity(claims);
}
return idk;
}
}
LdapRepository is nothing else as the DirectoryEntry and DirectorySearcher for active directory class.
I hope this helps you.

unable to use sessions in asp.net core "No service for ISession" Registered

i have followed official website docs to configure Session State
here is my startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromDays(10);
options.CookieHttpOnly = true;
});
services.AddMvc();
}
and this is Configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseSession();
app.UseStaticFiles();
app.UseDeveloperExceptionPage();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
however i get this error when trying to inject ISessioninto views
InvalidOperationException: No service for type 'Microsoft.AspNetCore.Http.ISession' has been registered.
as requested in comments i add view code also:
#inject ISession Session
nothing special.
you should register IHttpContextAccessor in your Startup.cs:
services.AddHttpContextAccessor();
And then you can inject it into your view:
#inject Microsoft​.AspNetCore​.Http.IHttpContextAccessor HttpContextAccessor
#{
var session = HttpContextAccessor.HttpContext.Session;
}
try use IHttpContextAccessor
#inject Microsoft​.AspNetCore​.Http.IHttpContextAccessor HttpContextAccessor
#if(HttpContextAccessor.HttpContext.Session ...)

Categories

Resources