Search code examples
asp.netauthorizationsignalrasp.net-core-signalr

Inform the user of unauthorized method call


I am currently developing a web application using SignalR Core. I am using an AuthorizeAttribute (without roles) to ensure only authenticated users will connect to our Hub. For certain methods a specific policy is required. These methods are also decorated with an AuthorizeAttribute containing a specific policy. Here is my Hub code:

[Authorize]
public class CustomerHub : Hub
{
    public async Task SimpleMethod()
    {
        // Do things that don't require additional authorization
        await Task.CompletedTask;
    }

    [Authorize(Policies.IsAdmin)]
    public async Task AdvancedMethod()
    {
        // Do things that require admin authorization
        await Task.CompletedTask;
    }
    
    public async Task ErrorMethod()
    {
        await Task.CompletedTask;
        throw new NotImplementedException();
    }
}

It all works as intended. I am able to connect to the Hub and call the SimpleMethod and the debugger does not step into the AdvancedMethod when I call it with insufficient rights. However when a user is not authorized to call a specific method I would like to inform them about this. But no specific error or message is sent back to the client in this case. I did implement a custom IHubFilter which informs the user of errors, but it turns out the AuthorizeAttribute is evaluated before this HubFilter is invoked.

    public class CustomHubExceptionsFilter : IHubFilter
    {
        public async ValueTask<object?> InvokeMethodAsync(HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object?>> next)
        {
            try
            {
                var result = await next(invocationContext);
                return result;
            }
            catch (Exception ex)
            {
                await invocationContext.Hub.Clients.Caller.SendCoreAsync("Error", new object?[] { $"Oops, something went wrong. Technical details: {ex.GetType().Name}" });

                throw;
            }
        }
    }

I also tried adding a custom AuthorizationHandler which returns an error when a user is unauthorized. However this causes the connection to be closed which is not a great solution in my opinion.

Is there a away to inform the user using AuthorizeAttributes? I could create a custom IHubFilter which checks whether the user is authorized. But that would require a lot of custom code. Is there a simpler/more native method available?


Solution

  • I suggest you could consider calling the Hub method's ErrorMethod directly inside the custom AuthorizationHandler inside of directly return the exception.

    For example:

    We need store the connection id inside the request, then inside the AuthorizationHandler we could get it and then use hub method to return the error to the client.

    Like this :

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ConnectionRequirement requirement)
    {
        var connectionIdClaim = context.User.FindFirst("connectionId");
        if (connectionIdClaim == null)
        {
            context.Fail();
            return;
        }
    
        var connectionId = connectionIdClaim.Value;
    
        await _hubContext.Clients.Client(connectionId).SendAsync("message", "You are not authorized to perform this action.");
    
        context.Fail();
    }