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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With