aspnetcore: Signal R Cookie Authentication not working with Blazor Server

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Blazor should connect automatically to Signal R Hub when using the Authorize attribute but it does not and gives a 401 unauthorized.

Hub Code

    [Authorize]
    public class ChatHub : Hub
    {
        public static HashSet<string> ConnectedUsers = new HashSet<string>();
        public const string HubUrl = "/chat";
        public async Task SendMessage(string user, string message, string room, bool join)
        {
            if (join)
            {
                await JoinRoom(room).ConfigureAwait(false);
                await Clients.Group(room).SendAsync("ReceiveMessage", user, " joined to " + room).ConfigureAwait(true);
            }
            else
            {
                await Clients.Group(room).SendAsync("ReceiveMessage", user, message).ConfigureAwait(true);
            }
            ConnectedUsers.Add(user);
        }

        public Task JoinRoom(string roomName)
        {
            return Groups.AddToGroupAsync(Context.ConnectionId, roomName);
        }

        public Task LeaveRoom(string roomName)
        {
            return Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
        }
}

Blazor server page code

protected override async Task OnInitializedAsync()
{
         _hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri(alpha_beta_backend.Hubs.ChatHub.HubUrl), options =>
         {
             options.UseDefaultCredentials = true;
         })
         .Build();
         _hubConnection.On<string, string>("ReceiveMessage", RecieveMessage);
}    
private async Task ConnectToRoom(string uEmail)
    {
        if (_hubConnection != null)
        {
            currentRoom = uEmail; //room is called what the userName is which is the email
            @* Connect to room *@
            await _hubConnection.StartAsync();
            await _hubConnection.SendAsync("JoinRoom", uEmail);
            messages.Add(new Message("Bot", "Connected", false));
            _connected = true;

        }
    }

Expected Behavior

Signal R should connect automatically when using the [Authorize] attribute on the Hub Class as stated here: https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-7.0 “Cookie authentication: When using the browser client, no extra configuration is needed. If the user is logged in to an app, the SignalR connection automatically inherits this authentication.”

Steps To Reproduce

dotnet 7 on m1 macos

Exceptions (if any)

The exception

System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()

.NET Version

7

Anything else?

No response

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 1
  • Comments: 15 (7 by maintainers)

Most upvoted comments

Hey @garrettlondon1 , About auth solution you found looks good but I will choice the ticket approach. I’m thinking like it’s has more control over it.

BTW; You are right that we can’t access the socket directly and add/trigger events but I managed to write a state manager with a single instance and connected other clients to it and wrote a chat application. We also tested it in production and saw that it did not cause problems with 100+ active users. When you implement this example, you are writing a pusher-like structure. You don’t need to open an extra socket. if you want you can go to the link and test it. Blazor Single Instance State

cannot tap into that to send our own events if we want realtime functionality in a Blazor Server app that has to be scaled (cannot support singleton shared state on one instance).

Yes, noted this in my original reply… once you want to scale your service, you have to rewrite it. You are locking yourself into a singleton implementation of whatever you are doing.

@Runaho you make a great point and this is overlooked. The main ComponentHub (or whatever default SignalR hub blazor server DOM updates run on), is successfully connected on the client, but we cannot tap into that to send our own events if we want realtime functionality in a Blazor Server app that has to be scaled (cannot support singleton shared state on one instance). Now I’m curious, is the SignalR Hub that Blazor Server runs on Authorized?

If you want to authorize another SignalR hub and you use Cookie auth, you have to do some hacky way in the _Host.cshtml

image

What I have done is pass the cookies from the .cshtml page, (the only place where httpcontext is safe to retrieve from), to the app. Once in App.razor, the cookies are stored in a Scoped appstate.

However, this needs a nasty implementation on the HubConnectionBuilder as seen below:

hubConnection = new HubConnectionBuilder()
                             .WithUrl(navigation.ToAbsoluteUri(SignalRHub.HubUrl), options =>
                             {
                                 options.UseDefaultCredentials = true;
                                 var cookieCount = User.Cookies.Count();
                                 var cookieContainer = new CookieContainer(cookieCount);
                                 foreach (var cookie in User.Cookies)
                                     cookieContainer.Add(new Cookie(
                                         cookie.Key,
                                         WebUtility.UrlEncode(cookie.Value),
                                         path: "/",
                                         domain: navigation.ToAbsoluteUri("/").Host));
                                 options.Cookies = cookieContainer;

```
                                 foreach (var header in User.Cookies)
                                     options.Headers.Add(header.Key, header.Value);

                                 options.HttpMessageHandlerFactory = (input) =>
                                 {
                                     var clientHandler = new HttpClientHandler
                                     {
                                         PreAuthenticate = true,
                                         CookieContainer = cookieContainer,
                                         UseCookies = true,
                                         UseDefaultCredentials = true,
                                     };
                                     return clientHandler;
                                 };
                             })
                             .WithAutomaticReconnect()
                             .Build();

This is the only thing I’ve found to work for authorized SignalR Hubs on Blazor Server applications using Cookie Auth.

I think this issue needs to be resurfaced and with .NET 8, there is so much focus on WASM when Blazor SSR + Websocket interactivity is equally as exciting to a large part of the community, especially that don’t understand auth well.

We need guidance as a community from Microsoft how to deal with this new Cascading HttpContext in SSR components, mix it with Websockets when you need, and call Web API controllers returning RazorComponentResult with HTMX if you don’t want to rely on websockets for an API call.

If these three things interacted with auth, in a clear and concise manner, Blazor would be unstoppable.