Building a Blazor WebAssembly App with a Blazor Server Admin Area

And only login once!
By: Steve Elliott

While developing my new social network at https://blazot.com (which is also built in Blazor), I reached a point of concern when it came time to start developing the admin area.  I've developed several smaller Blazor apps and wasn't too concerned about putting the administration area in the client, but it just doesn't feel quite right doing that on a large and important website.  I'm sure I can't be the only one who feels this way?  Besides the potential security concerns, why increase the Client project file size by adding code that doesn't really need to be there for the general user?  To me, the thing that makes the most sense is to use WebAssembly for the frontend, then handle administration through Blazor Server-Side.  The challenge was getting it to work in a way that recognized the authentication/authorization in BOTH the Blazor WebAssembly app and the Blazor Server app.  To be honest, everyone I asked about this just said it wasn't possible, but I just knew there had to be a way somehow.  After a lot of trial and error, searching, and frustration, I realized the issue really came down to how the policy was configured by the AddIdentityServerJwt helper method that's added in the Startup.cs file.

services.AddAuthentication()
	.AddIdentityServerJwt();

By default, this allows everything in the /Identity URL path to be handled by Identity and all other requests are handled by JwtBearerHandler.  If you reached this page through a search and have been stuck on this issue, this is likely the key to why you have had difficulty getting Blazor Server (or razor pages, etc.) to recognize authentication in an Area of your project that's using JSON Web Tokens (JWT).  The solution is to customize the policy and configure it to allow Identity to handle that specific Area as well.

services.AddAuthentication()
    .AddIdentityServerJwt()
    .AddPolicyScheme("PathConditionalAuthentication", null, options =>
    {
        options.ForwardDefaultSelector = (context) =>
        {
            if (context.Request.Path.StartsWithSegments(new PathString("/Identity"), StringComparison.OrdinalIgnoreCase) ||
                context.Request.Path.StartsWithSegments(new PathString("/Admin"), StringComparison.OrdinalIgnoreCase))
                return IdentityConstants.ApplicationScheme;
            else
                return IdentityServerJwtConstants.IdentityServerJwtBearerScheme;
        };
    });

services.Configure<AuthenticationOptions>(options => options.DefaultScheme = "PathConditionalAuthentication");

With this change, it's now possible to add a Blazor Server app to the "Admin" Area of a WebAssembly app and the authentication/authorization will work!  I personally believe this is the ideal way to setup a Blazor project, though it does require that you create your WebAssembly project as an ASP.NET Core hosted project.

I wanted to mention all of that first, since it was the issue I spent the most time working on and couldn't find anything in the documentation that addressed it.  And for many of you who may have attempted this, it's likely the main piece of the puzzle you were missing.

The GitHub Repo

I've also created a sample project to show this in action at https://github.com/ElliottBrand/BlazorWASMWithServerAdmin/.  Check it out, look through the code, and download it if you want to try it out.  Just to be clear, I'm not claiming this project is setup with great architecture, best practices, or anything like that.  There are several things I'd do differently if this was a real production application, but I wanted to keep it simple, lay out the general idea, and show that it can be accomplished.

Setting up the Admin Area

If you checkout my Startup file at https://github.com/ElliottBrand/BlazorWASMWithServerAdmin/blob/master/BlazorWASMWithServerAdmin/Server/Startup.cs, you'll be able to see what needed to happen to get Blazor-Server to run in an Area.  The two most important things to do is add Blazor Server-Side Services to the service collection with 

services.AddServerSideBlazor(); 

and setup the endpoints.

endpoints.MapBlazorHub("~/admin/_blazor");
endpoints.MapFallbackToAreaPage("~/admin/{*clientroutes:nonfile}", "/_AdminHost", "Admin");

The actual files that I created in the Admin Area are very similar to a default Blazor Server app, though I renamed some of them to better reflect their purpose. You'll want to pay extra close attention to https://github.com/ElliottBrand/BlazorWASMWithServerAdmin/blob/master/BlazorWASMWithServerAdmin/Server/Areas/Admin/Pages/_AdminHost.cshtml.  Notice the path in the page directive is "/admin/", the base href is "~/admin/" and the script at the bottom of the page has a forward slash in front of the path to blazor.server.js.

All the remaining information you need should be available in the source code of the aforementioned GitHub repo.  I hope this post has saved you some headache and if it helped you, please be sure to share it!!