Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Authorization for SSR/PreRendering interaction with Web APIs #60184

Open
1 task done
davisnw opened this issue Feb 4, 2025 · 0 comments
Open
1 task done

Improve Authorization for SSR/PreRendering interaction with Web APIs #60184

davisnw opened this issue Feb 4, 2025 · 0 comments
Labels
area-blazor Includes: Blazor, Razor Components
Milestone

Comments

@davisnw
Copy link

davisnw commented Feb 4, 2025

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

This originally started with the docs issue #34586 - IMovieService example can lead to authorization problems

Possibly related:

The current guidance for working with a Web API with SSR/Pre-rendering enabled is to create a server-side and a client-side service implementation.

I view this guidance as problematic from a security standpoint, as it leads to two different authorization stacks being used for the same essential data/operation, which can e.g. cause unintentional data leakage due to forgetting to update either the razor or the web api with the appropriate security requirements.

Copying from the Docs issue:

  • Sample project: 9.0/BlazorWebAppEntra/BlazorWebAppEntra.sln
  • When rendering in CSR, authorization is enforced by the /weather-forecast api (registered in BlazorWebAppEntra/Program.cs
  • For SSR/pre-rendering authorization is enforced by BlazorWebAppEntra.Client/Pages/Weather.razor
  • BlazorWebAppEntra/Weather/ServerWeatherForecaster.cs does not itself perform any authorization checks
  • Thus you now have two different authorization stacks in place for the same fundamental data/action.
    • In this particular case the authorization requirements are the same
    • However, one can quite easily imagine a scenario where the specs say that the data from IWeatherForecaster should be restricted by MyPolicy, and the dev only adds it to the endpoint registration (or vice versa, only adds it to the razor componet), leading to an authorization hole where depending on whether the page is rendered entirely CSR or SSR/pre-rendered you have different policies applied, leading to unintended information disclosure. This is particularly problematic as it can easily escape immediate user-testing as you would have to ensure that you actually access the page both as pure CSR rendering and as SSR/pre-rendered
    • This problem only gets worse as an application becomes more complex. In the particular case illustrated by the sample, there is a one-to-one correspondence between data exposed by the /weather-forecast endpoint and the Weather.razor page. Such a one-to-one correlation between an api and a razor component is not going to always exist, leading to even more opportunity for unintended information disclosure when different data has different authorization requirements
    • A potential solution would be to consolidate authorization logic into the ServerWeatherForecaster.cs, but that would appear to negate any declarative usage of the ASP.NET Core authorization stack altogether and could cause other problems (e.g., how would you force [Authorize("mypolicy")] attributes to be checked if they decorated the ServerWeatherForecaster or IWeatherForecaster? How would that authorization flow through to http 401 or 403 responses for unauthorized users calling the web api?)

cc: @guardrex dotnet/AspNetCore.Docs#34586

Describe the solution you'd like

A few broad options come to mind:

  1. Provide a mechanism to "inline" HttpClient calls

    • With today's framework, I believe you could omit the service abstractions and just call the Web API directly during pre-rendering by configuring an HttpClient in the Server project to pass-through the incoming user identity when calling the web api.
    • This should resolve concerns about ambiguity during authorization, since authorization for the service becomes the sole responsibility of the web api
    • However, doing so would contradict Service abstractions for web API calls:

      This section applies to Blazor Web Apps that maintain a web API in the server project or transform web API calls to an external web API.
      ...
      The server version can typically access the server-side resources directly. Injecting an HttpClient on the server that makes calls back to the server isn't recommended, as the network request is typically unnecessary.

    • This guidance appears to be primarily performance related, so could a mechanism be introduced to optimize HttpClient calls to the same application during pre-rendering?
  2. Provide a means to "unify" the authorization stacks between SSR/Pre-rendering and web api.

    • So for example, you could decorate your Service interface with with any of the authorization attributes supported by ASP.NET Core in a single place, and both blazor-pre-rendering and web api would honor them from that single interface
    • The goal is to have a single, unified authorization stack that applies no matter how the service is called (whether via web api, or SSR/pre-rendering), that is compatible with the current ASP.NET Core web api authorization stack.
  3. Provide a more streamlined mechanism to show e.g. a "loading" template for SSR/pre-rendering that resolves to the normal web api once CSR comes online

    • In one sense, this is "less ambitious", since it seeks to only pre-render the general page structure instead of pre-rendering the whole page with all data already retrieved
    • This is somewhat possible today, as referenced by Unsure how to inject HttpClient for InteractiveAuto render mode (RC2) #51468 (comment)

      if you want to change this you either inject a service to detect prerendering or use the OnAfterRenderAsync hook instead of the OnInitlializedAsync Hook. Or disable prerendering.

      protected override async Task OnAfterRenderAsync(bool firstRender)
       {
           if (firstRender)
           {
               Text = await _webScraper.Get();
               StateHasChanged();
           }
       }
      
    • However:
      • this approach does not make an appearance on the documentation page "Call a web API from ASP.NET Core Blazor", and I'm unsure if it has other downsides (my understanding how how Blazor rendering state works still has a lot of holes)
      • If you don't get the condition around calling StateHasChanged() correct, you can end up getting an infinite loop of calls to your web api
      • StateHasChanged() seems perhaps too coarse-grained? Thinking here about a component that calls multiple web apis, and perhaps references other components with their own api calls (Again, lots of holes in my current understanding of Blazor rendering state).

Additional context

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Feb 4, 2025
@javiercn javiercn added this to the Backlog milestone Feb 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

No branches or pull requests

2 participants