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?
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();
}