0

I have Angular SPA ASP.NET Core app with Identity (IdentityServer4). I use SignalR to push real-time messages to clients.

However I have to "broadcast" messages. All clients receive same messages regardless of what they require and then they figure out in Typescript - do they need this message or not.

What I want is to be able to decide which SignalR client should receive message and what content - it will make messages shorter and cut out processing time on clients completely.

I see there is hub.Client.User(userId) method - thats what I need.. However it appears that the Identity user ID is not known to SignalR.

If I override public override Task OnConnectedAsync() - context inside doesnt have any useful information eg user/principals/claims - are empty.

How can I find out which IdentityServer4 user is connecting to the hub?

EDIT1 suggested implementing IUserIdProvider doesnt work - all xs are null.

https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0#use-claims-to-customize-identity-handling

public string GetUserId(HubConnectionContext connection)
{
    var x1 = connection.User?.FindFirst(ClaimTypes.Email)?.Value;
    var x2 = connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    var x3 = connection.User?.FindFirst(ClaimTypes.Name)?.Value;
...

EDIT2 implemented "Identity Server JWT authentication" from https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0 - doesnt work either - accessToken is empty in PostConfigure

Boppity Bop
  • 9,613
  • 13
  • 72
  • 151

3 Answers3

2

You need to implement IUserIdProvider and register it in the services collection.

Check this question - How to user IUserIdProvider in .NET Core?

Stilgar
  • 22,354
  • 14
  • 64
  • 101
  • it is not working for me. In the `GetUserId` I have exactly same `context` object as I already have in `OnConnectedAsync` with empty User data. I dont know how to find out which user has connected or else I wouldnt need `GetUserId`. The only piece of data I have is `connectionId`.. I just need to find a way to correlate it with userId. – Boppity Bop Aug 01 '21 at 10:07
  • Well, I'm out of ideas. Does non-SignalR authentication work as expected? Normal controllers? – Stilgar Aug 01 '21 at 16:56
  • yeah.. i am going to make new question. i just did out of the box microsoft template angular app and added signalr. nothing else. auth doesnt work. – Boppity Bop Aug 01 '21 at 20:37
  • https://stackoverflow.com/questions/68614232/signalr-authorization-not-working-out-of-the-box-in-asp-net-core-angular-spa-wit – Boppity Bop Aug 01 '21 at 20:44
1

There is an obvious solution to it. Here is the sample one can use after creating an asp.net core angular app with identity.

Note that in this particular scenario (Angular with ASP.NET Core with Identity) you do NOT need to implement anything else, in contrary to multiple suggestions from people mis-reading the doc: https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0

Client side:

import { AuthorizeService } from '../../api-authorization/authorize.service';

. . .

constructor(. . . , authsrv: AuthorizeService) {

  this.hub = new HubConnectionBuilder()
    .withUrl("/newshub", { accessTokenFactory: () => authsrv.getAccessToken().toPromise() })
    .build();

Server side:

[Authorize]
public class NewsHub : Hub
{
    public static readonly SortedDictionary<string, HubAuthItem> Connected = new SortedDictionary<string, HubAuthItem>();

    public override Task OnConnectedAsync()
    {
        NewsHub.Connected.Add(Context.ConnectionId, new HubAuthItem
        {
            ConnectionId = Context.ConnectionId,
            UserId = Context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value
        });

        return base.OnConnectedAsync();
    }
}

Use it like this:

if(NewsHub.Connected.Count != 0)
    foreach (var cnn in NewsHub.Connected.Values.Where(i => !string.IsNullOrEmpty(i.UserId)))
        if(CanSendMessage(cnn.UserId)
            hub.Clients.Users(cnn.UserId).SendAsync("servermessage", "message text");
Boppity Bop
  • 9,613
  • 13
  • 72
  • 151
0

It is transpired that User data is empty in SignalR server context because authorization doesnt work as I expected it to. To implement SignalR authorization with Identity Server seems to be a big deal and is a security risk as it will impact the whole app - you essentially need to manually override huge amount of code which already is done by Identity Server just to satisfy SignalR case.

So I came up with a workaround, see my answer to myself here:

SignalR authorization not working out of the box in asp.net core angular SPA with Identity Server

EDIT: I missed an obvious solution - see the other answer. This is still valid workaround though, so I am going to let it hang here.

Boppity Bop
  • 9,613
  • 13
  • 72
  • 151