I am using [Authorize(Roles = "xxx")] in my Asp.Net Core Razor Pages application. It works fine but after some minutes (maybe 5) when I click Edit or Create button in my Crud, it sign out. How may I fix this? I guess the role is alive maybe just 5 minutes(a default time), but I don't know how to remove or change it.
Here is my StartUp 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 =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddIdentity<IdentityUser, IdentityRole>()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages().AddRazorRuntimeCompilation();
services.AddScoped<PagingParameter, PagingParameter>();
services.AddTransient<IEmailSender, EmailSender>();
services.AddReCaptcha(Configuration.GetSection("ReCaptcha"));
services.AddLocalization();
}
// 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.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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
}
}
Try to change the cookie ExpireTimeSpan:
services.ConfigureApplicationCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
});
You can refer to the doc for more details.
You got 2 options. As #mj1313 mentioned you can either use:
services.ConfigureApplicationCookie(options =>
{
options.SlidingExpiration = true; // instruct the handler to re-issue a new cookie with a new expiration time any time it processes a request which is more than halfway through the expiration window
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
});
and the other one is to pass expiration time in AuthenticationProperties while signing in:
var props = new AuthenticationProperties {
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(//put expiration time here)
};
Related
Setup
I have an ASP.NET Core MVC web app configured with Azure AD. I have an ASP.NET Core Web API configured Azure AD.
I obtain the token from Azure AD in the client web app and use it when requesting resources from the Web API, this works fine.
Issue
The '[Authorize(Policy = 'policyName')]' attribute returns 403 and User.IsInRole() returns false in the WEB API controller, however when I remove the authorize policy attribute and check the user claims object, I can see the role present. Please check the image below
Claims Image
RoleClaimType Image
In my startup file I have configured token validation parameters of roleClaimType to be roles
`services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>{
// Use the groups claim for populating roles
options.TokenValidationParameters.RoleClaimType = "roles";
});`
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.AddOptions();
services.AddMicrosoftIdentityWebApiAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
// Use the groups claim for populating roles
options.TokenValidationParameters.RoleClaimType = "roles";
});
services.AddAuthorization(options =>
{
options.AddPolicy(Constants.AssignmentToAccountCreatorsRoleRequired, policy => policy.RequireRole(Constants.CAN_CREATE_ACCOUNT));
});
services.AddDbContext<DbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
//services.BuildServiceProvider().GetService<DbContext>().Database.Migrate();
services.AddAutoMapper(typeof(Startup));
services.AddControllers().AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
//TODO: Replace CORS configuration
services.AddCors(c => c.AddDefaultPolicy(policy => {
policy.WithOrigins("https://localhost:44385", "https://someweb.net/")
.AllowAnyHeader()
.AllowAnyMethod();
}));
services.AddControllers();
services.AddScoped<ConfigurationModel>();
//services.AddScoped<GraphProfileClient>();
//services.AddScoped<GraphEmailClient>();
services.AddMemoryCache();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "NAPI", Version = "v1" });
});
}
// 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.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"));
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
I noticed the options for AddAuthorization() policy in the startup.cs contains a RequireClaim method which takes in the claimType and the allowedValue(s). RequireClaim(String, IEnumerable)
services.AddAuthorization(options =>
{
options.AddPolicy("AssignmentRoleRequired", policy => policy.RequireClaim("roles", "canDoSomething"));
}); //Startup.cs
[Authorize(Policy = "AssignmentToRoleRequired")] //For Controllers
Similarly the User Object also contains a method called HasClaim(), that takes in the claimType and the allowed value.
Within a controller HasClaim(String, String)
var result = User.HasClaim("roles", "canDoSomething"); //returns true
I have a Blazor server side app that uses authentication. I tried Azure SignalR as suggested by Visual Studio but after that when I am not authenticated I get a blank page instead of the typical not authorized webpage.
If I check the browser debug console, the following message appears:
"Error: Failed to complete negotiation with the server: Error: Unauthorized"
It looks this message is thrown by signalR.
If I change the line endpoints.MapBlazorHub().RequireAuthorization(); to endpoints.MapBlazorHub() in the startup.cs file, it runs as expected.
Any idea on how to fix this?
I tried rolling back the changes made by VS, but it still doesn't work as before.
Thank you
Edit 1: This is the app.cs code for your review:
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<NotAuthorized>
<h1>Restricted Access</h1>
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p>Page not found</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
Edit 2:
This is the startup 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.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<IdentityBDContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("IdentityBD"),
providerOptions => providerOptions.EnableRetryOnFailure()));
services.AddIdentity<CustomUser, IdentityRole>(options =>
{
options.User.RequireUniqueEmail = true;
options.SignIn.RequireConfirmedEmail = true; //prevents registered users from logging in until their email is confirmed.
}).AddRoles<IdentityRole>()
.AddEntityFrameworkStores<IdentityBDContext>()
.AddDefaultTokenProviders()
.AddUserManager<ERPUserManager>()
.AddSignInManager<ERPSignInManager>();
services.AddAuthorization(options =>
{
options.AddPolicy(SD.Admin, policy => policy.RequireRole(SD.Admin));
options.AddPolicy(SD.POS, policy => policy.RequireRole(SD.POS, SD.Admin));
options.AddPolicy(SD.AllowedTenant, policy => policy.Requirements.Add(new AllowedTenantRequirement(21)));
options.AddPolicy(SD.SysAdmin, policy => policy.RequireRole(SD.SysAdmin));
});
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
});
services.AddServerSideBlazor();
//services.AddSignalR().AddAzureSignalR();
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<CustomUser>>();
services.AddTransient<ConfigService>();
services.AddTransient<IdentityService>();
services.AddTransient<TenantService>();
services.AddHostedService<TimerUpdate>();
services.AddScoped<IAuthorizationHandler, AllowedTenantHandler>();
//Delete in production
services.AddServerSideBlazor().AddCircuitOptions(options => { options.DetailedErrors = true; });
services.AddScoped<ITenantProvider, WebTenantProvider>();
services.AddDbContext<ERPContext>(options => options
//.UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()))
.UseSqlServer(
Configuration.GetConnectionString("ERPDB")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetRequiredService<IdentityBDContext>();
context.Database.Migrate();
}
// Workaround for https://github.com/aspnet/AspNetCore/issues/13470
app.Use((context, next) =>
{
context.Features.Get<IHttpMaxRequestBodySizeFeature>().MaxRequestBodySize = null;
return next.Invoke();
});
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.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub().RequireAuthorization();
endpoints.MapControllerRoute("mvc", "{controller}/{action}");
endpoints.MapFallbackToPage("/_Host");
});
}
}
I have a new .NET Core 3.1 app and am struggling with the concept of Middleware. From reading around, it seems the order of including different middlewares is important. I currently have several problems which I can't seem to solve:
I never see the developer error page, and have to check the event log to see what's happened if there's an error. I just get the blank "error 500" etc pages from Chrome. The custom error pages also never display when there's a 500/400.
The app always tries to redirect me to /Account/Login despite changing this in the cookie settings.
User.IsAuthenticated returns false when the CheckPermissionsAction call is made in Elmah, so I can't access Elmah. The User.IsInRole call works for from controllers though.
This is how I'm bootstrapping the app. It feels like something is overriding the settings:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<DataProtectionTokenProviderOptions>(options =>
options.TokenLifespan = TimeSpan.FromDays(2));
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(30);
});
services.AddControllersWithViews();
services.AddTransient<IUserStore<User>, UserStore>();
services.AddTransient<IRoleStore<IdentityRole>, RoleStore>();
services.AddRazorPages();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = new PathString("/login");
options.AccessDeniedPath = new PathString("/error/denied");
options.LogoutPath = new PathString("/log-off");
options.ExpireTimeSpan = TimeSpan.FromDays(60);
options.SlidingExpiration = true;
options.Cookie.HttpOnly = true;
options.Cookie.Name = "MyCookie";
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
options.Cookie.SameSite = SameSiteMode.Lax;
});
services.AddIdentity<User, IdentityRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
})
.AddUserStore<UserStore>()
.AddRoleStore<RoleStore>()
.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.AddElmah<SqlErrorLog>(options =>
{
options.ConnectionString = Configuration.GetConnectionString("MyApp");
options.CheckPermissionAction = (context)=>{
return context.User.Identity.IsAuthenticated && context.User.IsInRole(RoleHelper.SuperAdmin);
};
options.Path = "/elmah";
});
services.AddSingleton<IAppConfiguration, AppConfiguration>(e => Configuration.GetSection("AppConfig")
.Get<AppConfiguration>());
OptionsConfigurationServiceCollectionExtensions.Configure<DbHelper>(services, Configuration.GetSection("ConnectionStrings"));
services.AddHttpContextAccessor();
}
public void ConfigureContainer(ContainerBuilder builder)
{
// wire up using autofac specific APIs here
builder.Register(context => new MapperConfiguration(cfg =>
{
cfg.CreateMap<User, MyDetailsViewModel>();
})).AsSelf().SingleInstance();
builder.RegisterModule(new RegistrationModule()); // separate assembly, wires up autofac registrations
builder.Register(c =>
{
//This resolves a new context that can be used later.
var context = c.Resolve<IComponentContext>();
var config = context.Resolve<MapperConfiguration>();
return config.CreateMapper(context.Resolve);
})
.As<IMapper>()
.InstancePerLifetimeScope();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
// debugger shows this section is called, but I never see the error page.
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseRouteDebugger();
}
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.UseSession();
app.UseElmah();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
var cookiePolicyOptions = new CookiePolicyOptions
{
Secure = CookieSecurePolicy.SameAsRequest,
MinimumSameSitePolicy = SameSiteMode.None
};
app.UseCookiePolicy(cookiePolicyOptions);
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Guest}/{action=Index}/{id?}");
endpoints.MapRazorPages();
endpoints.MapControllers();
});
app.UseStatusCodePages(async ctx =>
{
//Re-execute the request so the user gets the error page
string originalPath = ctx.HttpContext.Request.Path.Value;
switch (ctx.HttpContext.Response.StatusCode)
{
case 401:
//Re-execute the request so the user gets the error page
ctx.HttpContext.Items["originalPath"] = originalPath;
ctx.HttpContext.Request.Path = "/error/denied";
break;
case 412:
ctx.HttpContext.Items["originalPath"] = originalPath;
ctx.HttpContext.Request.Path = "/error/expired-account";
break;
case 404:
ctx.HttpContext.Items["originalPath"] = originalPath;
ctx.HttpContext.Request.Path = "/error/not-found";
break;
case 500:
ctx.HttpContext.Items["originalPath"] = originalPath;
ctx.HttpContext.Request.Path = "/error/not-found";
break;
}
});
DapperExtensions.DapperExtensions.SetMappingAssemblies(new[]
{
Assembly.GetAssembly(typeof(MyApp.Domain.Model.Note)),
Assembly.GetExecutingAssembly()
});
}
In regards to the order of your middleware, there is a problem with it.
There is a section in the Microsoft docs dedicated to the order of middleware, I suggest reading it.
As for your middleware, the correct order would be:
app.UseHttpsRedirection();
app.UseStatusCodePages(async ctx =>
{
// Omitted for brevity.
});
app.UseStaticFiles();
var cookiePolicyOptions = new CookiePolicyOptions
{
// Omitted for brevity.
};
app.UseCookiePolicy(cookiePolicyOptions);
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// If the app uses session state, call Session Middleware after Cookie
// Policy Middleware and before MVC Middleware.
app.UseSession();
app.UseElmah(); // Not sure about this one. I don't know what it's supposed to do?
app.UseEndpoints(endpoints =>
{
// Omitted for brevity.
});
DapperExtensions.DapperExtensions.SetMappingAssemblies(new[]
{
// Omitted for brevity.
});
Hello I'm trying to migrate a .Net Framework 4.6 application to asp.net core 2.2 and I'm block on the HttpContext.Session use.
I can call the SetString method, but on the second request the GetString return always null value.
I tried different answers found on Stackoverflow and official documentation but none of them are working on my case
public void ConfigureServices(IServiceCollection services)
{
var appConfiguration = new AppConfigurationManager(Configuration);
var allowedOrigins = appConfiguration.AllowedOrigins.Split(',').Select(s => s.Trim()).ToArray();
services.AddSingleton(Configuration); // Config
services.AddCors(o => o.AddPolicy("default", builder =>
{
builder.WithOrigins(allowedOrigins)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
})); // CORS
TokenVerifier.ControlToken(services, "secretToken");
services.AddSignalR();
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_2);
services.AddDistributedMemoryCache();
services.AddMvc().AddSessionStateTempDataProvider();
services.AddSession(options =>
{
options.Cookie.Name = "MySession";
options.IdleTimeout = TimeSpan.FromDays(1);
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
...
}
// 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
{
// 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.UseCors("default");
//app.UseHttpsRedirection();
app.UseAuthentication();
app.UseSignalR(routes =>
{
routes.MapHub<MindHub>("/myapp");
});
app.UseMiddleware<ExceptionMiddleware>();
app.UseSession();
app.UseMvc();
}
Note that JWT Authentication, CORS and Signalr are working (maybe helpfull for some of you)
Here is my final working sample code maybe usefull for some of you.
Note than the order is very important.
// 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)
{
string[] allowedOrigins = new string[]; // put your allowed origins here
services.AddSingleton(Configuration); // Config
services.AddCors(o => o.AddPolicy("default", builder =>
{
builder.WithOrigins(allowedOrigins)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
TokenVerifier.ControlToken(services, "secretToken");
services.AddSignalR();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.Configure<FormOptions>(x =>
{
x.ValueLengthLimit = int.MaxValue;
x.MultipartBodyLengthLimit = long.MaxValue; // In case of multipart
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); // HttpContext into ASP.NET Core
// Register your stuff here
}
// 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
{
// 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.UseCors("default");
app.UseAuthentication();
app.UseSignalR(routes =>
{
routes.MapHub<YourHub>("/hubName");
});
app.UseMiddleware<ExceptionMiddleware>();
app.UseHttpsRedirection();
app.UseMvc();
}
in our current setup we have a known path for logging in.
But now that we are using core 2.2 I cannot fix the current problem; That is always using loginPath: /Identity/Account/Login but we would like to change this.
Reading a lot on StackOverflow and others, I cannot seem to fix it.
So now i have a complete new MVC app trying to figure out what I am doing wrong.
In my startup I have:
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.AddIdentity<IdentityUser, IdentityRole>()
// .AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = new PathString("/Account/Login2");
});
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?}");
});
}
}
What am I doing wrong? Better yet; what is the solution :)
so as it turns out;
I needed to scaffold an identity item (like login) and in the login razor page (login.cshtml) you can add:
#page "~/account/login2"
this is probably also you can fix with the routing in razor during startup:
.AddRazorPagesOptions(options => {...});
haven't tried it out yet, but that is something else..
cheers