1

I am working on a Blazor Server app that authenticates through a third-party OAuth via Spotify.

services.AddAuthentication()
    .AddSpotify(options =>
    {
        options.ClientId = config.Spotify.ClientId;
        options.ClientSecret = config.Spotify.ClientSecret;

The user is able to login and create an account correctly. I have it configured to save the spotify access_token for later use so I can query the spotify API for this user.

ExternalLogin.cshtml.cs

await _signInManager.UpdateExternalAuthenticationTokensAsync(info);

Now, In addition to using SignalR via the Blazor Server functionality, I'm also using a SignalR client to connect to my server on the "front end" page for the current user.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
    endpoints.MapHub<HostHub>("/hubs/host");
});

So when the user navigates to the page that needs the server in Hub.razor:

@inject HubConnectionProvider HubProvider

    public async Task StartHub()
    {
        try
        {
            Hub = HubProvider.GetHostHub();
            Hub.On<TrackStatus>(nameof(ISpotiFollowerContract.SetTrack), SetTrack);
            await Hub.StartAsync();

            await JoinGroupAsync();
...

//HubConnectionProvider.cs
    public HubConnection GetHostHub()
    {
        string baseUrl = _navMan.BaseUri;
        var hubUrl = baseUrl.TrimEnd('/') + "/hubs/host";
        return new HubConnectionBuilder()
            .WithUrl(hubUrl, options =>
            {
                //my question lies here
                options.AccessTokenProvider = () => Task.FromResult(_currentUserService.GetAccessToken());
            })
            .Build();
    }

The problem I'm having is that even though the user is authenticated for the Blazor app, I can't the authentication to get the user in my HostHub.cs:

        public async Task JoinGroup(string userId)
        {
            //Name comes through as null, Identity has no claims, etc.
            var name = Context.User.Identity.Name;

I've tried providing the HubConnectionBuilder > options.AccessTokenProvider a value, but I'm not sure which access code I should be providing. Since I'm authenticating my user through a third-party token, should I be providing the Spotify access_token I store for the user, or should I be generating a token for MY app that I send to authenticate my user for the HostHub I'm connecting to?

I've tried sending the Spotify access token, and still get no claims on my user. Is there something I need to do to allow my app to authenticate based on this token? I've tried generating an access token for my application for the already-logged-in user, but haven't found a way to do so. If that's what I need to do, how can I generate this token?

DLeh
  • 23,806
  • 16
  • 84
  • 128
  • That's because the hub allows anonymous calls. A Blazor Server app is still a server-side ASP.NET Core app and uses the same mechanisms for authentication. The [Authorize users to access hubs and hub methods](https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0#authorize-users-to-access-hubs-and-hub-methods) section in the docs explains: `By default, all methods in a hub can be called by an unauthenticated user. To require authentication, apply the Authorize attribute to the hub` – Panagiotis Kanavos Apr 07 '21 at 07:25

2 Answers2

0

I use this with a hub that Authenticates against Identity Server 4 or AzureAD.

hubConnection = new HubConnectionBuilder()
    .WithUrl(uri, options => {
        options.AccessTokenProvider = async () => {
            var accessTokenResult = await accessTokenProvider.RequestAccessToken();
            accessTokenResult.TryGetToken(out var accessToken);
            var token = accessToken.Value;
            return token;
        };                    
    })
    .WithAutomaticReconnect()
    .Build();

services.AddAuthentication()
    .AddMicrosoftAccount(options =>
    {
        options.ClientId = microsoftAccount.ClientId;
        options.ClientSecret = microsoftAccount.ClientSecret;
        options.SignInScheme = microsoftAccount.SignInScheme;
    });
[Authorize]
public class MessageServiceHub : Hub
{
    ...
Brian Parker
  • 11,946
  • 2
  • 31
  • 41
  • The important part is the `Authorize` attribute. Without it, no matter what kind of authentication is configured, SignalR will allow unauthenticated access – Panagiotis Kanavos Apr 07 '21 at 07:25
  • @PanagiotisKanavos And the AccessTokenProvider is a `Func>` that is called after the user is Authenticated. – Brian Parker Apr 07 '21 at 07:30
  • @BrianParker what is the implementation of `accessTokenProvider`? This is what I cannot find. I don't want to implement another authentication scheme, my app is designed around spotify and cannot function without it, so I'd like that to be the sole way to authenticate. – DLeh Apr 07 '21 at 14:17
  • @DLeh This is code that i copied and pasted from a working project. The token I send is from the host project. Ill find a link to a repo give me a few minutes – Brian Parker Apr 07 '21 at 14:19
  • @DLeh https://github.com/BrianLParker/SignalRAuth this is self hosted. It does not have the external provider configured but that is not the issue. It could be added and it would still work.. https://stackoverflow.com/a/63120607/1492496 – Brian Parker Apr 07 '21 at 14:24
  • it seems like the `IAccessTokenProvider` is in blazor webassembly and i'm using blazor server- https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-5.0 – DLeh Apr 07 '21 at 14:27
  • @DLeh Enet has posted several articles on here re getting tokens from blazor-server. Sorry I don't do Blazor server and I missed that in your question. – Brian Parker Apr 07 '21 at 14:32
  • @BrianParker i'm not familiar with Enet, could you give me a link to that? – DLeh Apr 07 '21 at 16:07
0

You need to add the Authorize attribute to the hub.

A Blazor Server app is mostly a server-side ASP.NET Core app and uses the same mechanisms for authentication. SingalR behaves the same way it does for any other ASP.NET Core application. The Authorize users to access hubs and hub methods section in the Authentication and authorization in ASP.NET Core SignalR doc page explains that:

By default, all methods in a hub can be called by an unauthenticated user. To require authentication, apply the Authorize attribute to the hub

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • I added `[Authorize]` to my hub, now on `Hub.StartAsync()` i get a 401 response, so I'm on the right track. Providing `options.AccessTokenProvider` with the spotify OAuth token still gives 401. What access token should I be giving it? – DLeh Apr 07 '21 at 13:54
  • I injected HttpContextAccessor into my .razor page, i can't find any sign of a current `access_token` within the headers. this code returns null: `var context = httpContextAccessor.HttpContext; var access = await context.GetTokenAsync("access_token");` – DLeh Apr 07 '21 at 14:00
  • @DLeh Did you try my code to get the token?? I know it works.. Is your `HubConnection` in a service or .razor page. A common mistake is creating the hubconnection before the client is Authed. If it is in a .razor page add `@attribute [Authorize]` to the top of the page. – Brian Parker Apr 07 '21 at 14:13
  • Debugging the .razor page, the httpContext.User is authenticated correctly. adding `@attribute [Authorize]` to razor had no effect. The HubConnection is created in a service that is injected into the .razor page. In that service the user is authenticated, i've checked. – DLeh Apr 07 '21 at 14:23