Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SignalR Hub Authorization for Blazor WebAssembly with Identity

I have set up a SignalR Blazor WebAssembly app with JWT authentication, configured to send the token through query field access_token.

At the server, I see that it is assigning the context.Token from this value.

However, for the hub, annotated with [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)], I am getting 401.

Why is it not authorizing?

This is the code for the hub: https://github.com/jonasarcangel/SignalRAuthTest/blob/master/SignalRAuthTest/Server/SignalR/MessagesHub.cs

This is what I have in Startup.cs: https://github.com/jonasarcangel/SignalRAuthTest/blob/master/SignalRAuthTest/Server/Startup.cs

And this is client: https://github.com/jonasarcangel/SignalRAuthTest/blob/master/SignalRAuthTest/Client/Pages/SignalR.razor.cs

like image 895
Jonas Arcangel Avatar asked Dec 14 '22 08:12

Jonas Arcangel


1 Answers

This works with the WebAssembly self hosted Identity server with [Authorize] attribute on the hub (which uses bearer tokens...).

What is important is configuring of a function to get the token on Hub start. The hub connection can use whatever transport mechanism it needs to send the access token.

 hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/chathub"), options =>
            {
                options.AccessTokenProvider = async () =>
                {
                    var accessTokenResult = await AccessTokenProvider.RequestAccessToken();
                    accessTokenResult.TryGetToken(out var accessToken);
                    return accessToken.Value;
                };
            })
            .Build();

The full page I adapted from the 3.1 SignalR tutorials.

@using Microsoft.AspNetCore.SignalR.Client
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.AspNetCore.Authorization

@page "/chat"
@attribute [Authorize]
@implements IDisposable

<div class="form-group">
    <label>
        User:
        <input @bind="userInput" />
    </label>
</div>
<div class="form-group">
    <label>
        Message:
        <input @bind="messageInput" size="50" />
    </label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>

<hr>

<ul id="messagesList">
    @foreach (var message in messages)
    {
        <li>@message</li>
    }
</ul>

@code {
    private HubConnection hubConnection;
    private List<string> messages = new List<string>();
    private string userInput;
    private string messageInput;

    [Inject]
    public NavigationManager NavigationManager { get; set; }

    [Inject]
    public IAccessTokenProvider AccessTokenProvider { get; set; }

    protected override async Task OnInitializedAsync()
    {

        hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/chathub"), options =>
            {
                options.AccessTokenProvider = async () =>
                {
                    var accessTokenResult = await AccessTokenProvider.RequestAccessToken();
                    accessTokenResult.TryGetToken(out var accessToken);
                    return accessToken.Value;
                };
            })
            .Build();

        hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
        {
            var encodedMsg = $"{user}: {message}";
            messages.Add(encodedMsg);
            StateHasChanged();
        });

        await hubConnection.StartAsync();
    }

    Task Send() =>
        hubConnection.SendAsync("SendMessage", userInput, messageInput);

    public bool IsConnected =>
        hubConnection.State == HubConnectionState.Connected;

    public void Dispose()
    {
        _ = hubConnection.DisposeAsync();
    }
}

Here is a working repo

Here are the changes I made to the standard WebAssembly with Identity project. Other than the page I posted I basically followed the WebAssembly Signalr tutorial

In your solution: SignalR.razor

 hubConnection = new HubConnectionBuilder()
             .WithUrl(NavigationManager.ToAbsoluteUri("/messageshub"), options =>
             {
                 options.AccessTokenProvider = async () =>
                 {
                     var accessTokenResult = await tokenProvider.RequestAccessToken();
                     accessTokenResult.TryGetToken(out var accessToken);
                     return accessToken.Value;
                 };
             })
            .Build();

In Startup.cs change this back to the template.

services.AddAuthentication()
                  .AddIdentityServerJwt();

Then just [Authorize] on your hub.

Your code working

like image 147
Brian Parker Avatar answered Mar 23 '23 00:03

Brian Parker