-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Problem providing Access Token to HttpClient in Interactive Server mode #52390
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
Comments
Have you looked at using a DelegatingHandler to manage this? Here is a YouTube video by @m-jovanovic that shows you how: Middleware Pattern For HttpClient With Delegating Handlers. |
@gragra33 yes I do have a DelegatingHandler in the project: https://github.com/RobJDavey/BlazorTokenIssue/blob/main/BlazorApp/AuthenticationStateHandler.cs This does not appear to get given the configured Based on this guidance, as this handler could be called from an interactive context, it cannot just grab the token off the |
@RobJDavey based on your implementation namespace BlazorApp.Services;
public class TokenProvider
{
public string? AccessToken { get; set; }
public string? RefreshToken { get; set; }
} builder.Services.AddScoped<TokenProvider>(); You should have a Here is what your implementation should be: public class TokenProviderFactory
{
private IServiceProvider _serviceProvider;
public TokenProviderFactory(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
public GenerateTokenProvider<TSettings>() where TSettings : IProviderSettings
{
var settings = _serviceProvider.GetRequiredService<IOptions<TSettings>>().Value;
return new TokenProvider()
{
AccessToken = settings.AccessToken;
RefreshToken = settings.RefreshToken;
};
}
} remove: builder.Services.AddScoped<TokenProvider>(); and replace with: builder.Services.AddScoped<TokenProviderFactory>(); now update: var tokenProvider = _circuitServicesAccessor.Services?.GetRequiredService<TokenProviderFactory>().GenerateTokenProvider<GitHubSettings>(); *Note: the above code is typed freehand here and not tested but should work as-is. I am sure that there is enough there to help solve your issue. |
@gragra33 the token provider is not for client access tokens, but user tokens retrieved at login via OpenIDConnect, so they cannot be stored in settings. The token provider is configured to have the current user's access token in the App component which is based on this guide. This works fine in server side scenarios, but not in interactive. |
@RobJDavey I have not run your code, so I did not dig that deeply. Now I have. However, have you set breakpoints in:
Is the I would also add a constructor in the |
@gragra33 the construction of the The first time is for the server side rendered version of the page which is getting the access token set correctly, and is successfully making the HTTP call to the external service. The second time is for the interactive client services, which as I understood it is what the circuit services accessor is supposed to help with. Maybe I have misunderstood what this is for. As this is creating a new version of the Ideally when the Blazor Hub receives a message I'd be able to just grab the token from the user's One approach I have tried which does seem to work is using the |
@RobJDavey I noticed that @mkArtakMSFT tagged your issue with I set a breakpoint in the What I observed was that the component was created twice. The first time, your token was passed correctly, the So when you navigate to the
This is happening when NavManager is used (via the NavLink component) and also with a regular anchor html element. This does not happen with
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:
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. |
This is the exact issue I am having. And I am new to blazor, not new to asp.net or DI. but it also seems like a lot of this is very new in Blazor so finding information has been difficult. The result i was hoping for is that the scope of the prerender or the scope of the http request that runs through the middleware would be the same scope that is then used on the interactive rendering side. But that is not the case. Currently, I'm thinking the only option I have is to register a singleton token class keyed on some value from the user or cookie to then retrieve the token instance based on that key value. But obviously any custom token handle carries security risks. Let me know if y'all found any solutions to this. |
@RobJDavey @wesleyscaldwell There is this recent blog post Per-User Blazor 8 State by @rockfordlhotka. I have not tried this, due to a recent emergency that we are still resolving, however this looks like a possible interim solution until Microsoft releases one. |
I haven't read the full post but my understanding from the intro is that if I disable the prerender and use only interactive I should be good. This is a start up project and I'm just trying to get it going. Once we have more usage I can focus on optimizations. If this works I'll report back. |
@gragra33 I tested this by disabling prerendering in hopes of a quick solution and all that did was disable the rendering portion, but the underlying DI scopes remain the same. The initial scope is created and return the UI and then the socket scope starts up without the context. So the Per-User Blazor 8 State by @rockfordlhotka option is exactly what i was thinking, but he has clearly covered this in a much more extensive way. But after reading it all, I find that when I'm this far down a rabbit hole on something that I have to imagine would be a common request, I have to think i'm missing something. Is there any chance we have all missed some other option that makes it possible to share the scope between the prerender and the interactive session? In the meantime, I am going to make use of what @rockfordlhotka has started on and store user properties in a singleton. |
I just implemented a simplified version of Per-User Blazor 8 State (I am not using wasm) and it seems to be working fine. I agree with @wesleyscaldwell that it feels like we are missing something about sharing scope between prerender and the interactive session. |
I implemented a similar process. But I decided to add unique session id user claim on the OIDC OnUserInformationReceived Event. This then enables to me get the session from the session service as in the repo you referenced. Having a unique session ID added by default would have saved a lot of the difficulty of this task. My implementation is buried deep now in other code, but i can strip it out if anyone thinks it would be helpful to see. |
We'll look into how we can make it easier to retrieve saved access tokens from interactive server components in .NET 9, but I do think that reading the access token from the I'll also look into updating the https://learn.microsoft.com/en-us/aspnet/core/blazor/security/server/additional-scenarios?view=aspnetcore-8.0#pass-tokens-to-a-server-side-blazor-app doc to use |
My opinion is that having a session token to be referenced between the signalr connection and the httpcontext would enable a lot of use cases beyond just the access token. In my case I wanted the session ID for an authenticated user. But I could even see an instance where you want a session ID synced between the two for non authenticated users. And I could also see the signalr connection starting before the authenticated context is available. But I might be recalling that you need to reload the UI after authenticating. I am relatively new to blazor. So I be overlooking some basic details in my response. |
Is not being able access the |
Right now |
If you're migrating from You can just call Lines 39 to 47 in 1ee90c2
|
@halter73 then there's something missing in the Blazor Web App template, because it does set up server-rendered, server-interactive, and wasm-interactive models. And in that template, if you try to access the My assumption, therefore, is that Microsoft intends for us to use |
@rockfordlhotka Are you trying to access the |
Yes, like many (most?) people, I have assemblies that include business rules that depend on having access to the current user identity. Putting such business logic directly in the UI is what has gotten us in trouble since VB3, and is a bad practice. Until now, it has been possible to gain access to the current user from any code in an app - via the current thread, HttpContext, ApplicationStateProvider, or other means depending on which .NET environment is hosting the code. This includes Blazor interactive modes, where Right now, in Blazor 8, it appears that the solution (for server code) is to detect whether a circuit is active, so This is my prototype solution: |
Thanks for contacting us. We're moving this issue to the |
Closing as a dupe of #52379 |
I am here look at and reading this because we need to retrieve the access_token from httpContext and some times and only ever in Firefox, http context isn't availible. I have read that using http context isn't recommended, and am exploring the circuithandler solution. The access token is not available in the AuthenticaitonStateProvider. I thought about capturing the token, it isn't clear which lifecyle i could use that starts off with a http context, and goes on to become a circuit handler. It also feels like i'd l'd need to call something like a 'settoken' in those early stages - and that feels icky. Hopefully the Circuit Handler fix here helps - or if anyone can give a strong nudge on the Authentication State being able to help? |
Is there an existing issue for this?
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 theWeatherService
, and grab the token from it there. This works fine when using server side rendering, but the token isnull
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 theCircuitServicesAccessor
. The circuit handler however never seems to get theCreateInboundActivityHandler
, and so I have been unable to test whether theTokenProvider
it would provide would contain anull
access token or not.Expected Behavior
When a HttpClient request is made in InteractiveServer mode, the
TokenProvider
configured in theApp
component should be passed to theWeatherService
class so it can be used to set theAuthentication
header.Alternatively, the
AuthenticationStateHandler
class should get theCircuitServicesAccessor
set by theServicesAccessorCircuitHandler
, which can then be used to access theTokenProvider
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
orbob
/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.
.NET Version
8.0.100
Anything else?
cc: @guardrex dotnet/AspNetCore.Docs#31113
The text was updated successfully, but these errors were encountered: