I just figured out how to do this in .NET 7.0 RC1 (I don't see why it wouldn't work in 5.0 and 6.0 too, but haven't tested). So far it seems to work, but will see if I come across any issues...
In Program.cs
:
// so I can access login page static assets. See extension method below
app.UseStaticAlwaysWhenPathsUniversal();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// restrict Blazor WASM client to users in specific role(s). See extension method below
app.UseRestrictPagesForNonPrivilegedUsers(Authentication.LoginPath);
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
/// <summary>
/// I don't want Blazor WASM accessible (at all, not even loadable) before logging in as employee user, but still make static assets
/// accessible to identity pages (e.g. login). In the future we may have customers logging in but they should not
/// be able to access Blazor WASM client.
/// </summary>
/// <param name="app">The web application</param>
public static void UseStaticAlwaysWhenPathsUniversal(this WebApplication app)
{
app.UseWhen(
AlwaysAllowStaticContent, // see method below
a => a.UseStaticFiles()
);
}
/// <summary>
/// Redirect to specified page whenever non-privileged user tries to access an address that is not allowed.
/// Use for customers in the future. Don't allow them to access Blazor WASM client.
/// </summary>
/// <param name="app">The web application</param>
/// <param name="redirectTo">The path to redirect to if the logged in user tries to access a path
/// that is not in 'allowedPaths'</param>
public static void UseRestrictPagesForNonPrivilegedUsers(this WebApplication app, string redirectTo)
{
app.Use(async (context, next) =>
{
var isEmployee = context.User.HasClaim(claim => claim.Type == ClaimTypes.Role && claim.Value == "Employee");
if (isEmployee || AlwaysAllowStaticContent(context))
{
await next.Invoke();
return;
}
context.Response.Redirect(redirectTo);
});
}
private static bool AlwaysAllowStaticContent(HttpContext context)
{
return context.Request.Path.StartsWithSegments("/connect") || // my login/logout pages start with /connect
context.Request.Path.StartsWithSegments("/_content") ||
context.Request.Path.StartsWithSegments("/lib") ||
context.Request.Path.StartsWithSegments("/favicon.ico");
}
Update 1:
To simplify things and just prevent access to framework files (i.e. my dll files etc), I changed my code to this. The down-side to this approach is that if a non-privileged user is logged in and tries to navigate to the Blazor WASM site, the "loading" page will show but then it will just freeze on that page.
In Program.cs
:
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// restrict Blazor WASM client to users in a specific role. See extension method below
app.UseBlockFrameworkFilesForNonPrivilegedUsers("Employee");
app.UseBlazorFrameworkFiles();
/// <summary>
/// Forbid access to Blazor WASM framework files for a user that is not in specified <paramref name="privilegedRole"/>.
/// </summary>
/// <param name="app">The web application</param>
/// <param name="privilegedRole">The role a user must have to access Blazor WASM</param>
public static void UseBlockFrameworkFilesForNonPrivilegedUsers(this WebApplication app, string privilegedRole)
{
app.Use(async (context, next) =>
{
var isInPrivilegedRole = context.User.HasClaim(claim => claim.Type == ClaimTypes.Role && claim.Value == privilegedRole);
if (isInPrivilegedRole || !IsFrameworkPath(context))
await next.Invoke();
else
context.Response.StatusCode = StatusCodes.Status403Forbidden;
});
}