Search code examples
jwtsignalrblazorblazor-client-side

Using JWT during SignalR connection with Blazor-WASM


I'm messing with Blazor + SignalR connection. I'd want to Authorize calls to SignalR by using JWT.

Basically I want to attach to SignalR calls the JWT

Here's my Blazor WASM SignalR Code

@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager NavigationManager
@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;

    protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
            .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();
    }
}

But I'm not sure how to attach JWT to this

I've seen this in Js version in section

Bearer token authentication in

this.connection = new signalR.HubConnectionBuilder()
    .withUrl("/hubs/chat", { accessTokenFactory: () => this.loginToken })
    .build();

https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.1#authenticate-users-connecting-to-a-signalr-hub

What's Blazor's way of doing this?

I tried this:

var token = "eyJhb(...)";

hubConnection = new HubConnectionBuilder()
.WithUrl($"{Configuration["Url"]}/chathub", (HttpConnectionOptions x) =>
{
    x.Headers.Add("Authorization", $"Bearer: {token}");
})
.Build();

But it threw error:

Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: The format of value 'Bearer: eyJh' is invalid.
System.FormatException: The format of value 'Bearer: eyJhbG' is invalid.

Solution

  • The solution was... to read the docs

    var token = "eyJ";
    
    hubConnection = new HubConnectionBuilder()
        .WithUrl($"{Configuration["Url"]}/chathub?access_token={token}")
        .Build();
    

    Token is provided at connection estabilishing via url

    We need to modify startup.cs to support OnMessageReceived

    docs url:

    https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.1#authenticate-users-connecting-to-a-signalr-hub

    services.AddAuthentication(options =>
    {
        // Identity made Cookie authentication the default.
        // However, we want JWT Bearer Auth to be the default.
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        // Configure the Authority to the expected value for your authentication provider
        // This ensures the token is appropriately validated
        options.Authority = /* TODO: Insert Authority URL here */;
    
        // We have to hook the OnMessageReceived event in order to
        // allow the JWT authentication handler to read the access
        // token from the query string when a WebSocket or 
        // Server-Sent Events request comes in.
    
        // Sending the access token in the query string is required due to
        // a limitation in Browser APIs. We restrict it to only calls to the
        // SignalR hub in this code.
        // See https://learn.microsoft.com/aspnet/core/signalr/security#access-token-logging
        // for more information about security considerations when using
        // the query string to transmit the access token.
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                var accessToken = context.Request.Query["access_token"];
    
                // If the request is for our hub...
                var path = context.HttpContext.Request.Path;
                if (!string.IsNullOrEmpty(accessToken) &&
                    (path.StartsWithSegments("/hubs/chat")))
                {
                    // Read the token out of the query string
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            }
        };
    });