Secure Your .NET 5 Blazor Server App with MFA

Blazor is an exciting new technology from Microsoft that will allow developers to bring C# to clients. Server and client components are written in the same language and can be used and re-used interchangeably. Blazor comes in two flavors, server and client apps. In this tutorial you will be working with Server Blazor apps, where the C# code is run on the server, and messages are exchanged using SignalR.


This is a companion discussion topic for the original entry at https://developer.okta.com/blog/2022/01/07/blazor-server-side-mfa

I’m doing WASM 6.0.1 and I see my razor pages will honor a policy name like this…
@attribute [Authorize(Policy = "SomePolicyName")]

when I have a simple policy like this…

	options.AddPolicy("IsTrue", policy => policy
		.RequireAssertion(_ => true)
	);

And yet this fails…

	options.AddPolicy("IsJohnDoe", policy => policy
		.RequireUserName("John.Doe@Company.com")
	);

Instead I have to write something like this…

	options.AddPolicy("IsJohnDoe", policy => policy
		.RequireAssertion(_ =>
		{
			var username = _.User.Claims.SingleOrDefault(_ => _.Type == "preferred_username")?.Value;
			return "John.Doe@Company.com".Equals(username, StringComparison.OrdinalIgnoreCase);
		})
	);

And while this works…

	options.AddPolicy("HasGroups", policy => policy
		.RequireClaim("MyGroupArray")
	);

this will fail…

	options.AddPolicy("HasGroup1", policy => policy
		.RequireClaim("MyGroupArray", "Group1")
	);

and so I do this…

	options.AddPolicy("HasGroup1", policy => policy
		.CustomRequireClaim("MyGroupArray", "Group1")
	);

with this extension method…

	public static class CustomAuthorizationPolicyBuilder
	{
		static Func<Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext, string, string[], bool> _CustomRequireClaim
			= (ctx, claimName, allowedValues) => {
				var jsonArray = ctx.User.Claims.SingleOrDefault(_ => _.Type == claimName).Value;
				var userValues = JsonSerializer.Deserialize<string[]>(jsonArray).ToHashSet<string>(StringComparer.OrdinalIgnoreCase);
				return allowedValues.Any(allowedValue => userValues.Contains(allowedValue));
			};

		public static Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder CustomRequireClaim(
			this Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder me,
			string claimName,
			params string[] allowedValues
		)
			=> me.RequireAssertion(ctx => _CustomRequireClaim(ctx, claimName, allowedValues));
	}

I didn’t have any of these challenges while securing WebAPI endpoints with policies.
Perhaps I should have installed the Okta.AspNetCore nuget package in my WASM project (I have that in my WebAPI)?