aspnetcore
aspnetcore copied to clipboard
MapRazorComponents broken with FallbackPolicy RequireAuthenticatedUser
Is there an existing issue for this?
- [X] I have searched the existing issues
Describe the bug
It seems fallback policy is broken for all blazor modes.
services.AddAuthorization(o => o.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
...
app.UseStaticFiles();
app.UseAuth...();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
results in a redirect to login when requesting /_framework/blazor.web.js.
services.AddAuthorization(o => o.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
...
app.UseStaticFiles();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.UseAuth...();
ALSO results in a redirect to login when requesting /_framework/blazor.web.js.
Expected Behavior
Putting Maps before UseAuth... => no auth check Putting Maps after UseAuth... => auth check for pages, but not for staticfiles
Steps To Reproduce
See above
Exceptions (if any)
No
.NET Version
8.0.100-rc.2.23502.2
Anything else?
No response
Thanks for contacting us.
We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.
Is there any kind of workaround here or timeline to a fix?
I've ended up putting @attribute {Authorize] which will use the default policy on all of my blazor components which by default is that a user needs to be authenticated, but can be adjusted if needed.
https://github.com/dotnet/AspNetCore.Docs/issues/31931 shows another example of someone else running into the same issue but for blazor.server.js which is a regression. Previously, it was much easier to just add the UseStaticFiles() before UseAuthorization() if you didn't want to require authentication for Blazor's static resources.
The workarounds I suggest could be used such as applying the [Authorize] attribute in the _Imports.razor file all have shortcomings. It makes me think that maybe we should add a simple boolean option to add [AllowAnonymous] to all of Blazor's static endpoints.
@halter73 I ran into this - was getting 302 redirected on the _framework path. The solution you commented in the other issue is good as a workaround, but note that all the _framework paths will have the same issue.
I initially only checked for the specific path that was triggering the challenge but quickly discovered it needed to be a starts-with check.
I worked around it with a handler (have since disabled the "global auth" setting due to other issues, though.)
public class BlazorFrameworkAuthorizationMiddlewareResultHandler(ILogger<BlazorFrameworkAuthorizationMiddlewareResultHandler> log) : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler handler = new();
public async Task HandleAsync(
RequestDelegate next,
HttpContext context,
AuthorizationPolicy policy,
PolicyAuthorizationResult authorizeResult)
{
if (IsFrameworkPath(context.Request.Path) && authorizeResult.Challenged)
{
log.LogInformation("Bypassing challenge for _framework files");
// allow required framework paths to be accessible anonymously
await handler.HandleAsync(next, context, policy, PolicyAuthorizationResult.Success());
return;
}
await handler.HandleAsync(next, context, policy, authorizeResult);
}
private bool IsFrameworkPath(string path)
{
return path.StartsWith("/_framework");
}
}
This is my workaround for a static SSR project:
AuthorizationPolicy defaultDeny = new AuthorizationPolicyBuilder()
.RequireAssertion( context => context.Resource is HttpContext { Request : { Method: "GET", Path.Value: "/_framework/blazor.web.js" } } )
.Build();
builder.Services.AddAuthorizationBuilder()
.SetFallbackPolicy( defaultDeny )
// other authz stuff
;
This seems to work well, and loudly reminds me when I have forgotten to explicitly set the authorization policy for an endpoint.
What follows is yet another way to work around the problem. Defining the Fallback policy caused a lot of weird undesirable behavior, but we still wanted to protect our static files. I built what is essentially a wrapper for the StaticFilesMiddleware to ensure authentication before delivering the files.
public class CustomStaticFileMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv,
IOptions<StaticFileOptions> options, ILoggerFactory loggerFactory, IAuthorizationService authorizationService)
{
private readonly RequestDelegate _next = next;
private readonly StaticFileMiddleware _staticFileMiddleware = new(next, hostingEnv, options, loggerFactory);
private readonly IAuthorizationService _authorizationService = authorizationService;
public Task Invoke(HttpContext context)
{
var result = _authorizationService.AuthorizeAsync(context.User, null, new OurUserRequirement()).Result;
if (result.Succeeded)
{
return _staticFileMiddleware.Invoke(context);
}
else
{
return _next(context);
}
}
}
The UseStaticFile() call is just a wrapper around a UseMiddleWare call to the StaticFileMiddleware so the replacement was luckily very simple.
app.UseMiddleware<CustomStaticFileMiddleware>(Options.Create(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "Static")),
RequestPath = $"/static"
}));
::EDIT:: Changed code to reflect better implementation. Older suggestion used async which could cause the app to crash. ::END EDIT::
I've ended up putting
@attribute {Authorize]which will use the default policy on all of my blazor components which by default is that a user needs to be authenticated, but can be adjusted if needed.
This sounds like it works, but defeats the purpose of having a fallback policy. Sheesh.
Am i the only one who wants to protect all pages except a few?
@boomalator no, you are certainly not.
Does anyone know if this was fixed in .net 9 ?
I tried I upgraded my solution to .net 9.0 and looked through this: https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-9.0#blazor without any luck in figuring it out
Does anyone know if this was fixed in .net 9 ?
I tried I upgraded my solution to .net 9.0 and looked through this: https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0?view=aspnetcore-9.0#blazor without any luck in figuring it out
I have upgraded and deployed our solution using .net 9 and we still have to create workarounds for this Issue has milestone as .net10 planning, so there is that
Thank you @jonas-nordtorp 👌😊
I ended up adding specific _Imports.razor files to different areas or folders.
Here's an illustration of what I mean:
This is my workaround for a static SSR project:
AuthorizationPolicy defaultDeny = new AuthorizationPolicyBuilder() .RequireAssertion( context => context.Resource is HttpContext { Request : { Method: "GET", Path.Value: "/_framework/blazor.web.js" } } ) .Build(); builder.Services.AddAuthorizationBuilder() .SetFallbackPolicy( defaultDeny ) // other authz stuff ;This seems to work well, and loudly reminds me when I have forgotten to explicitly set the authorization policy for an endpoint.
this is actually the best workaround our of all, simply check in the browser debugger what files get a 302 (Found) code, and add their file path into the new AuthorizationPolicy
But you might encounter an issue where you will suddenly lose access to everything after this so don;t forget to add an exception that the user is allowed to have access to other files either based on the fact that they are logged in (or ideally in the future based on custom claims) here is my real-life fix in Blazor (Server Side) Web App - using .NET9 (still broken in .NET9).
// Define a default deny policy with an exception for the `_framework/blazor.web.js` file
var defaultDeny = new AuthorizationPolicyBuilder()
.RequireAssertion(context =>
context.User.Identity != null && (context.User.Identity.IsAuthenticated ||
// Allow specific GET requests for static assets
context.Resource is HttpContext
{
Request:
{
Method: "GET", Path.Value:
"/_framework/blazor.web.js" or
"/lib/bootstrap/dist/css/bootstrap.min.xxx.css" or
"/app.xxx.css" or
"/RiskAssessmentApp.Web.xxx.styles.css" or
"/favicon.png"
}
} ||
// Allow POST requests to the authentication endpoints
context.Resource is HttpContext
{
Request:
{
Method: "POST", Path.Value:
"/YourExternalLoginUrl" or
"/YourLogoutUrl"
}
} ||
// Allow anonymous access to the authentication callback URL
context.Resource is HttpContext { Request: { Method: "GET", Path.Value: "/signin-microsoft" } }))
.Build();
services.AddAuthorizationBuilder()
.SetFallbackPolicy(defaultDeny);
You want to call the above before calling:
var app = builder.Build();
This is implicitly fixed in dotnet 10 with the blazor infrastructure files being part of static files.