aspnetcore: Problem providing Access Token to HttpClient in Interactive Server mode

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When making authenticated requests in InteractiveServer mode, a user’s access token is required to talk to an external service. As the guidance is that it is not safe to use the IHttpContextAccessor when in server interactive mode, I have been following the documentation to try and add a token provider servicer.

I’ve followed the documentation guides below, however the access token on the token provider in interactive server mode always comes through as null. I’m unsure if I have misunderstood or missed something in these guides or if the guides do not currently lead to a complete solution.

The sample project I have attached tries two different approaches to get this access token.

The first is the inject the TokenProvider into the WeatherService, and grab the token from it there. This works fine when using server side rendering, but the token is null when in interactive server mode.

The second is to try use a circuit handler to set the correct services for the circuit, allowing it to access the TokenProvider from inside other services by injecting the CircuitServicesAccessor. The circuit handler however never seems to get the CreateInboundActivityHandler, and so I have been unable to test whether the TokenProvider it would provide would contain a null access token or not.

Expected Behavior

When a HttpClient request is made in InteractiveServer mode, the TokenProvider configured in the App component should be passed to the WeatherService class so it can be used to set the Authentication header. Alternatively, the AuthenticationStateHandler class should get the CircuitServicesAccessor set by the ServicesAccessorCircuitHandler, which can then be used to access the TokenProvider class to get the token.

Steps To Reproduce

I have created a sample solution which shows what I have attempted so far: https://github.com/RobJDavey/BlazorTokenIssue

The README explains how to run the solution. While there are 3 projects in the solution, 2 of them are purely there to support the demonstration, it’s only the BlazorApp service that is at issue.

To authenticate, please user the either username and password alice/alice or bob/bob as these are the test users configured.

The SSR page always loads the data from the external service fine, however the Interactive Server page fails due to the missing token.

Exceptions (if any)

A 401 is returned by the service when no valid access token is attached.

System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at BlazorApp.Services.WeatherForecastService.GetForecastAsync(Int32 skip, Int32 take) in ./BlazorApp/Services/WeatherForecastService.cs:line 34
   at BlazorApp.Components.Pages.WeatherInteractive.<OnInitialized>b__8_0(GridItemsProviderRequest`1 request) in ./BlazorApp/Components/Pages/WeatherInteractive.razor:line 47

.NET Version

8.0.100

Anything else?

cc: @guardrex https://github.com/dotnet/AspNetCore.Docs/issues/31113

About this issue

  • Original URL
  • State: open
  • Created 7 months ago
  • Comments: 22 (4 by maintainers)

Most upvoted comments

@RobJDavey I noticed that @mkArtakMSFT tagged your issue with Doc. It made me curious, so I downloaded your solution and ran it.

I set a breakpoint in the OnInitializedAsync method in the App.razor component, OnInitialized method in the WeatherInteractive.razor component, and in the GetForecastAsync method in WeatherForecastService. I then navigated to the page. and observe the order of breakpoints being hit.

What I observed was that the component was created twice. The first time, your token was passed correctly, the OnInitialized was called, and the data was collected from your server via the WeatherForecastService. The second time, the OnInitialized was called, as it was a new instance, no token was passed to the new WeatherForecastService instance, and you obviously received a 401 response, as you should. This is because prerendering is enabled by default (more information on render modes here: ASP.NET Core Blazor render modes)

So when you navigate to the WeatherInteractive page, I am seeing the following sequence of breakpoints being hit:

App.razor                // new instance and sets token
WeatherInteractive.razor // initialised
WeatherForecastService   // set token is passed
WeatherInteractive.razor // initialised (again) / new instance
WeatherForecastService   // new instance and empty token is passed

This is happening when NavManager is used (via the NavLink component) and also with a regular anchor html element.

This does not happen with StreamRendering. So when you navigate to the WeatherSsr page, I am seeing the following sequence of breakpoints being hit:

App.razor              // new instance and sets token
WeatherSsr.razor       // initialised
WeatherForecastService // set token is passed

If I set the render mode to no prerender:

@rendermode @(new InteractiveServerRenderMode(prerender: false))

… then I am seeing the following sequence of breakpoints being hit:

App.razor                // new instance and sets token
WeatherInteractive.razor // initialised
WeatherForecastService   // new instance and empty token is passed

I have not isolated why this is happening with your code and there is no simple work-around that I can see. I am not sure if this is a bug or by-design. Someone from Microsoft will have to chime in.