How to protect events listened to in SignalR?

Viewed 55

Let's say I have this Hub in an ASP.NET backend.

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.SendAsync("ReceiveMessage", user, message);
}

I know that I can protect this SendMessage method with the attribute Authorize like this.

public class ChatHub : Hub
{
    [Authorize]
    public async Task SendMessage(string user, string message)
        => await Clients.All.SendAsync("ReceiveMessage", user, message);
}

That way only Authorized users can call this endpoint.
But are still able to connect to ChatHub and listen to "ReceiveMessage".

In an Angular frontend app I am using the signalR.HubConnection from '@microsoft/signalr' to listen to events like this.

this.hubConnection.on('ReceiveMessage', (data: SomeData) => {
  this.SomeDataSub.next(data);
});

My question is, How can I ensure that users can only listen to enpoints they are allowed to?
So, in case someone listens to "ReceiveMessage" they will get: "you aren't allowed to listen to this event."

1 Answers

I don't know any way to prevent clients from listening to any event (I'm not saying it's impossible). I just want to suggest a different approach that might help. You can add users to groups according to their roles. Then send sensitive events to the appropriate group.

public class ChatHub : Hub
{
    [Authorize]
    public async Task SendMessage(string user, string message)
    {
        // protected event that only users with "admin" role should receive
        await Clients.Group("administrators").SendAsync("ReceiveMessage", user, message);
    }

    public override async Task OnConnectedAsync()
    {
        var connectionId = Context.ConnectionId;

        if (Context.User.IsInRole("admin"))
        {
            await Groups.AddToGroupAsync(connectionId, "administrators");
        }

        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception exception)
    {
        var connectionId = Context.ConnectionId;

        if (Context.User.IsInRole("admin"))
        {
            await Groups.RemoveFromGroupAsync(connectionId, "administrators");
        }
            
        await base.OnDisconnectedAsync(exception);
    }
}
Related