I have an application that authenticates using OpenId. The code in the startup is:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = new PathString("/account/login");
})
.AddOpenIdConnect("CustomScheme", options =>
{
// options configured
});
I have a hub and wire up the hub in the app.UseEndpoints as follows:
endpoints.MapHub<SpecialMessageHub>("myspecialhub");
Outside of posting my entire Startup.cs file, here is a quick overview:
app.UseAuthentication();andapp.UseAuthorization();are before theapp.UseEndpointsservices.AddSignalR();andservices.AddServerSideBlazor();come after theservices.AddAuthenticationandservices.AddAuthorization
I've simplified my Hub for this question:
using Example.Extensions;
using Example.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System.Threading;
using System.Threading.Tasks;
namespace Example.Hubs
{
[Authorize]
public class SpecialMessageHub : Hub
{
public override Task OnConnectedAsync()
{
var customerId = Context.User.GetCustomerId();
Groups.AddToGroupAsync(Context.ConnectionId, GetCustomerGroupName(customerId));
return base.OnConnectedAsync();
}
public Task SendMessage(string customerId,
SpecialMessage message,
CancellationToken cancellationToken)
{
return Clients.Groups(GetCustomerGroupName(customerId)).SendAsync("ReceiveMessage", message, cancellationToken);
}
public static string GetCustomerGroupName(string customerId) => $"customers-{customerId}";
}
}
**Note: GetCustomerId is an extension method in my project
I have a simple component just to test this:
@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@using Example.Models
@inject Microsoft.AspNetCore.Components.NavigationManager NavigationManager
@implements IAsyncDisposable
<h2>@myMessage.Id</h2>
<div>@myMessage.Message</div>
@code {
private HubConnection hubConnection;
private SpecialMessage myMessage = new SpecialMessage();
protected override async Task OnInitializedAsync()
{
if (!IsConnected)
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/myspecialhub"))
.WithAutomaticReconnect()
.Build();
hubConnection.On<string, SpecialMessage>
("ReceiveMessage", (user, message) =>
{
myMessage = message;
StateHasChanged();
});
await hubConnection.StartAsync().ConfigureAwait(false);
}
}
public bool IsConnected => hubConnection != null
&& hubConnection.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
await hubConnection?.DisposeAsync();
}
}
I then add that component into my view with the below:
<component type="typeof(SpecialMessageComponent)" render-mode="ServerPrerendered"/>
When I hit this view, it returns a 401:
Based on this https://docs.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.1, the cookie should be passed to the hub:
In a browser-based app, cookie authentication allows your existing user credentials to automatically flow to SignalR connections. When using the browser client, no additional configuration is needed. If the user is logged in to your app, the SignalR connection automatically inherits this authentication.
I am able to verify the user is authenticated and has the claim I am looking for both in the controller and in the component. I feel like this should be fairly straightforward and wouldn't require multiple hops, so any help would be greatly appreciated.
Here are some of the things I have tried:
Pull both the "access_token" and "id_token" from the Context within the View and pass it to the component as a parameter. Then based on here https://docs.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.1#bearer-token-authentication, I did something like this, but this still resulted in a 401:
[Parameter] public string AccessToken { get; set; } protected override async Task OnInitializedAsync() { if (!IsConnected) { hubConnection = new HubConnectionBuilder() .WithUrl(NavigationManager.ToAbsoluteUri("/myspecialhub"), options => { options.AccessTokenProvider = () => Task.FromResult(AccessToken); }) .WithAutomaticReconnect() .Build(); hubConnection.On<string, SpecialMessage> ("ReceiveMessage", (user, message) => { myMessage = message; StateHasChanged(); }); await hubConnection.StartAsync().ConfigureAwait(false); } } }I tried to manipulate the User by replacing the
ClaimsPrincipalwith a newIIdentitywith theAuthenticationTypeset toCookieAuthenticationDefaults.AuthenticationScheme. This resulted in a nasty infinite loop but after restarting the app, the Identity was swapped, however, I still received a 401.I have tried other options as well, but I cannot seem to get this to work with the OpenId.
