Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Blazor WebAssembly SignalR Authentication

I would love to see an example on how to add authentication to a SignalR hub connection using the WebAssembly flavor of Blazor. My dotnet version is 3.1.300.

I can follow these steps to get an open, unauthenticated SignalR connection working: https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr-blazor-webassembly?view=aspnetcore-3.1&tabs=visual-studio

All the tutorials I find seem older or are for a server-hosted type, and don't use the built-in template.

I have added authentication to the rest of the back-end, using the appropriate template and these instructions, including the database: https://docs.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1

But every time I add [Authenticate] to the chat hub, I get an error returned. Is there any way, extending the first tutorial, that we can authenticate the hub that is created there? It would be great to hitch on to the built-in ASP.NET system, but I am fine just passing a token in as an additional parameter and doing it myself, if that is best. In that case I would need to learn how to get the token out of the Blazor WebAssembly, and then look it up somewhere on the server. This seems wrong, but it would basically fill my needs, as an alternative.

There are all sorts of half-solutions out there, or designed for an older version, but nothing to build off the stock tutorial that MS presents.

Update: Following the hints in this news release https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-preview-2-release-now-available/, I now can get a token from inside the razor page, and inject it into the header. I guess this is good?? But then how do I get it and make use of it on the server?

Here is a snippet of the razor code:

protected override async Task OnInitializedAsync()
{
    var httpClient = new HttpClient();
    httpClient.BaseAddress = new Uri(UriHelper.BaseUri);

    var tokenResult = await AuthenticationService.RequestAccessToken();

    if (tokenResult.TryGetToken(out var token))
    {
        httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.Value}");

        hubConnection = new HubConnectionBuilder()
            .WithUrl(UriHelper.ToAbsoluteUri("/chatHub"), options =>
            {
                options.AccessTokenProvider = () => Task.FromResult(token.Value);
            })
            .Build();
    }
}

Update 2: I tried the tip in here: https://github.com/dotnet/aspnetcore/issues/18697

And changed my code to:

        hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/chatHub?access_token=" + token.Value))
            .Build();

But no joy.

like image 722
Andrew Avatar asked May 28 '20 21:05

Andrew


People also ask

Does Blazor WebAssembly use SignalR?

Blazor Server. With the Blazor Server hosting model, the app is executed on the server from within an ASP.NET Core app. UI updates, event handling, and JavaScript calls are handled over a SignalR connection using the WebSockets protocol.

How do you secure Blazor WebAssembly?

Blazor WebAssembly apps are secured in the same manner as single-page applications (SPAs). There are several approaches for authenticating users to SPAs, but the most common and comprehensive approach is to use an implementation based on the OAuth 2.0 protocol, such as OpenID Connect (OIDC).

How do I add authentication to Blazor?

There is an option available to enable authentication for the Blazor app when you create the application. To enable authentication for Blazor server app, click on “Change” under “Authentication section and select “Individual User Accounts” option and then click on “Ok” button when you create a new Blazor server app.

What is SignalR Blazor?

blazor. server. js allows the app to establish a SignalR connection over the network to handle UI updates and event forwarding between the Blazor app running in the browser and our ASP.NET Core app running on the server. You can find a reference to this js script in the Pages\ _Host. cshtml file.


1 Answers

I've come across the same issue.

My solution was 2-sided: I had to fix something in the fronend and in the backend.

Blazor

In your connection builder you should add the AccessTokenProvider:

string accessToken = "eyYourToken";
connection = new HubConnectionBuilder()
    .WithUrl("https://localhost:5001/hub/chat", options =>
    {
        options.AccessTokenProvider = () => Task.FromResult(token.Value);
    })
    .Build();

options.AccessTokenProvider is of type Func<Task<string>>, thus you can also perform async operations here. Should that be required.

Doing solely this, should allow SignalR to work.

Backend

However! You might still see an error when SignalR attempts to create a WebSocket connection. This is because you are likely using IdentityServer on the backend and this does not support Jwt tokens from query strings. Unfortunately SignalR attempts to authorize websocket requests by a query string parameter called access_token.

Add this code to your startup:

.AddJwtBearer("Bearer", options =>
{
    // other configurations omitted for brevity
    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"))) // Ensure that this path is the same as yours!
            {
                // Read the token out of the query string
                context.Token = accessToken;
            }
            return Task.CompletedTask;
        }
    };
});

edit 1: Clarified the usage of the Blazor SignalR code

like image 76
Sander van 't Einde Avatar answered Oct 23 '22 05:10

Sander van 't Einde