Disable prebuilt Razor UI pages after scaffolding identity - asp.net-mvc

In VS2022, I started a new Razor page project with "Individual Accounts" selected as authenization type. I found that there are no identity-related code available for customization because they are prebuilt using the Razor UI. I want to customize the pages and so I generated the pages by the method suggested as below.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-6.0&tabs=visual-studio&viewFallbackFrom=aspnetcore-2.1
After that, I wanted to disable the registration page after creating my admin account. I selected "Exclude from project" to Register.chtml as shown below.
Surprisingly, I found that the prebuilt Razor UI pages are automatically used when the generated pages are excluded(I can still access and use the url /Identity/Account/Register). How can I disable the prebuilt Razor UI pages?

You can add app.Map middleware like below:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.Map("/Identity/Account/Register", HandleRequest);
app.Map("/Identity/Account/Login", HandleRequest);
static void HandleRequest(IApplicationBuilder app)
{
app.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
await context.Response.WriteAsync("404 Not Found");
});
}
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}

When you include the UI package, you opt in to using all of it. You can selectively disable pages by scaffolding them and then returning a NotFoundResult from both the OnGet and OnPost methods, but that can be laborious. You can also use Authorization to prevent access to pages or folders by specifying an authorization policy that is never applied to users, or a non-existent role, for example:
[Authorize(Roles ="Does Not Exist")]
public class RegisterModel : PageModel
{
...
Or, if you no longer need the Identity UI at all, uninstall the package from your application.

Related

.net attribute routes not matched in production

I have a asp.net v6 mvc project with an spa front end that works fine in development. (Following the new pattern where my spa dev server has a proxy configuration to forward api requests to the .net app).
When I deploy this to IIS, my api routes (all beginning "api/") are no longer recognised, and all api requests are redirected to the fallback spa home page. What's going on ?
This is my Configure method, in Startup.cs
public void Configure(IApplicationBuilder builder, IWebHostEnvironment env)
{
...
builder.UseStaticFiles();
builder.UseRouting();
builder.UseEndpoints(endpoints =>
{
// I added this line to try and fix my problem. It's not necessary in dev, but still broken in prod.
endpoints.MapControllers();
// This was all I had in dev, but all my Controller actions have AspNetCore.Mvc Route attributes.
endpoints.MapControllerRoute(
name: "default",
pattern: "api/{controller}/{action=Index}/{id?}");
// I know this method is called and used, because if I change "index.html" everything breaks and I'm no longer redirected to my spa
endpoints.MapFallbackToFile("index.html");
});
}
Here is a controller...
using Microsoft.AspNetCore.Mvc;
[ApiController]
public class AllItemsController : ControllerBase
{
[Route("api/all-items")]
public ApiResponse<List<Item>, string> Get()
{

Getting 401 Unauthorized with MVC Pages while Identity Razor pages work as expected

Background
I am doing a POC to find out if Angular, Razor and MVC pages work seamlessly in a web application. I started with Visual Studio template named "ASP.NET Core with Angular". I have selected "Individual Accounts" to include default authentication functionality. This creates an Angular app with a secure web API endpoint (WeatherForecast) and provides basic user registration, login, logout, user profile pages etc features built in. So far all works well, when I try to fetch data from the protected API (WeatherForecast) I get redirected to the Identiy/Account/Login razor page where I can login and then get redirected back to Angular and I can see that data is returned and grid is populated. Till this point everything works fine.
The Problem
I added a DemoController class with a basic "Hello World" HTML view. When I try to access this new page with /demo, it works as expected. However, when I apply [Authorize] attribute to the controller, I get 401 Unauthorized. I checked on server side that User.IsAuthenticated property is set to false despite having successfully logged in before. Now interesting observation is that the user profile page (which is protected and works only if there an active login) works fine.
Please note that all API calls issues from Angular use JWT bearer token and work fine. When I try to access user profile page, it does NOT use JWT, it uses cookies to authenticate. The GET request to /demo page also has all these cookies in headers, still it is met with 401.
I spent a lot of time going thru articles, searching web with no success. The closing thing we found is this : ASP.NET Core 5.0 JWT authentication is throws 401 code
But that didn't help either.
The project is created using Visual Studio 2022, .net core 6.0. Here is the Program.cs file for your reference:
using CoreAngular.Data;
using CoreAngular.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
builder.Services.AddAuthentication()
.AddIdentityServerJwt();
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();
app.MapFallbackToFile("index.html"); ;
app.Run();
This has been answered here: https://stackoverflow.com/a/62090053/3317709
It turned out that using IdentityServer extension methods add a policy scheme such that only /Identity pages have cookie authentication. The rest default to JWT.
We can customize this by adding our own policy like so:
builder.Services.AddAuthentication()
.AddIdentityServerJwt()
.AddPolicyScheme("ApplicationDefinedAuthentication", null, options =>
{
options.ForwardDefaultSelector = (context) =>
{
if (context.Request.Path.StartsWithSegments(new PathString("/Identity"), StringComparison.OrdinalIgnoreCase) ||
context.Request.Path.StartsWithSegments(new PathString("/demo"), StringComparison.OrdinalIgnoreCase))
return IdentityConstants.ApplicationScheme;
else
return IdentityServerJwtConstants.IdentityServerJwtBearerScheme;
};
});
// Use own policy scheme instead of default policy scheme that was set in method AddIdentityServerJwt
builder.Services.Configure<AuthenticationOptions>(options => options.DefaultScheme = "ApplicationDefinedAuthentication");

How to configure MVC on a Blazor WebAssembly Hosted Solution

I have a Blazor WebAssembly Hosted solution (Client and Server) setup with IdentityServer for Authentication. I am looking to do 2 things...
I would like to set up MVC on the Server since I am more comfortable with MVC. The idea for Server Side pages would be for things like Profile Management and accessing Content that I do not want to on the Client.
The Server Startup.cs currently has
public void ConfigureServices(IServiceCollection services)
{
....Condensed
services.AddControllersWithViews();
services.AddRazorPages();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext dataContext)
{
.....Condensed
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
With MVC setup on the backend, how can I navigate to these pages from the Client?
When you create your WebAssembly solution, be sure to check the box "ASP.Net Core Hosted".
This will create three projects: Client, Shared, and Server.
In the server project you will find a Controllers folder. Go ahead and add a controller, such as DummyController.cs
namespace BlazorWASM4.Server.Controllers
{
[ApiController]
[Route("[controller]")]
public class DummyController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
Then right-click on your controller method Index and click 'Add View'. Then implement the view (Index.cshtml) like this for example:
<h1>Dummy Page</h1>
Run the project and navigate to localhost:port/Dummy
You should see your new page get displayed.

ASP.NET Core 3.0 Custom Error Page not reachable

I have created a simple error.cshtml razor page in the Pages folder. Pages folder is at the same level as the Views folder, so it is at the correct level.
Error.cshtml page is similar to the markup and code shown here:
#page
<h2>Sorry... Try again later...</h2>
In the Startup.cs I have the following code
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
//app.UseDefaultFiles();
app.UseStaticFiles();
app.UseNodeModules();
app.UseRouting();
app.UseEndpoints(cfg => {
cfg.MapControllerRoute("Fallback", "{controller}/{action}/{id?}",
new { controller = "App", action = "Index" });
});
}
I made sure that env is not a Development environment so it calls app.UseExceptionHandler("/Error"). However when there is an exception is thrown, I get HTTP ERROR 404 "localhost page cannot be found" message instead of getting my custom error page.
I am not sure why my error page is not loading. Any help is appreciated.
According to your description, I found you don't add the razor page's endpoint mapping setting. This is the reason why you faced the 404 error.
I suggest you could try to add the endpoints.MapRazorPages(); in the app.UseEndpoints method, then it will work well.
Details, you could refer to below codes:
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Default}/{action=Index}/{id?}");
});
If you don't inject the razor page, you should also add the below codes in the ConfigureServices method:
services.AddRazorPages();
Result:

Running a blazor app inside of an Area inside of a MVC project

I'm trying to add a new Area to my MVC project that will contain a new app created in Blazor .
I've added the new Area and copied all files from a template blazor project to this Area. It compiles, and I even get Intellisense on my components.
If I run my project now, my starting page is always the blazor app, while it should have been the MVC site's starting page. I only want the Blazor environment if my user surfs to "myapplication.co/blazor" (or something like that).
Steps I undertook to add a Blazor area:
Added a new Area to my existing MVC app
Added calls to AddServerSideBlazor and routing in my Startup.cs class
public void ConfigureServices(IServiceCollection services)
{
/* ... */
services.AddServerSideBlazor();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseEndpoints(endpoints =>
{
// the controller route for the seperate area:
endpoints.MapAreaControllerRoute(
name: "BlazorArea",
areaName: "Blazor",
pattern: "{area:exists}/{controller}/{action=Index}/{id?}");
//the original controller route
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
endpoints.MapBlazorHub();
}
}
In my Blazor Area, I copied the following files from a new Blazor project
Does anyone have any idea how I can achieve this? If more code is needed, please let me know and I will post more code.
Thank you very much for any ideas on this!

Resources