2

I followed the below solution and moved code into library project. It works fine if I have hub connection in Blazor Server project but doesn't work when moved to library project:

Adding cookies to SignalR Core requests

I have below service in the Library Razor project, that hold implementation of hub connection setup and is shared among two Blazor server apps:

public class UserHubService : IAsyncDisposable
{
  private HubConnection? _hubConnection;
  public async Task<bool> InitializeHubAsync(string baseUrl, string hubPath, string subscribeToGroup, Guid? siteId, string userName)
  {
    UserName = userName;

    var uri = new Uri(new Uri(baseUrl), hubPath);

    //_hubConnection = new HubConnectionBuilder()
    //    .WithUrl(new Uri(new Uri(baseUrl), hubPath))
    //    .Build();

    _hubConnection = new HubConnectionBuilder()
    .WithUrl(uri, opti =>
    {
        if (_httpContextAccessor.HttpContext != null)
            foreach (var c in _httpContextAccessor.HttpContext.Request.Cookies)
            {
                opti.Cookies.Add(new Cookie(c.Key, c.Value)
                {
                    Domain = uri.Host, // Set the domain of the cookie
                    Path = "/" // Set the path of the cookie
                });
            }
    })
    .Build();

    _hubConnection.On<Guid?, ProductModel, string>(SignalR_Method.TouchProductReceiveNotification, async (siteID, product, messageType) =>
    {
        if (_subscribedMessageTypes.Contains(messageType))
        {
            await HandleNotificationAsync(siteID, product, messageType);
        }
    });

    try
    {
        await _hubConnection.StartAsync();
        await _hubConnection.InvokeAsync(subscribeToGroup, siteId);
        return true;
    }
    catch (Exception ex)
    {
        return false;
        // Handle exception (e.g., log it)
    }
  }
}

The above service is injected in page and called from Server app:

await ProductNotificationHubService.InitializeHubAsync(baseUrl, hubPath, SignalR_Method.SubscribeToTouchSiteGroup, Site.Site_ID, _userInfo.UserName)

Below is the hub again in Library razor project:

[Authorize]
public class NotificationHub : Hub
{
    private static readonly ConcurrentDictionary<string, List<string>> UserConnections = new();

    [Authorize]
    public override Task OnConnectedAsync()
    {
        var userEmail = Context.User?.FindFirst(ClaimTypes.Email)?.Value;
        //var userName = Context.User?.Identity?.Name; // Assuming the username is stored in the Name claim

        if (!string.IsNullOrEmpty(userEmail))
        {
            UserConnections.AddOrUpdate(
               userEmail,
               new List<string> { Context.ConnectionId }, // Add a new list with the current connection ID
               (key, existingConnections) =>
               {
                   if (!existingConnections.Contains(Context.ConnectionId))
                   {
                       existingConnections.Add(Context.ConnectionId); // Add the connection ID to the existing list
                   }
                   return existingConnections;
               });
        }

        return base.OnConnectedAsync();
    }

    [Authorize]
    public override Task OnDisconnectedAsync(Exception exception)
    {
        var userEmail = Context.User?.FindFirst(ClaimTypes.Email)?.Value;
        var connectionID = Context.ConnectionId;

        if (!string.IsNullOrEmpty(userEmail))
        {
            if (UserConnections.TryGetValue(userEmail, out var connections))
            {
                // Remove the specific connection ID
                connections.Remove(Context.ConnectionId);

                // If no more connections exist for this user, remove the user entry from the dictionary
                if (connections.Count == 0)
                {
                    UserConnections.TryRemove(userEmail, out _);
                }
            }
        }

        return base.OnDisconnectedAsync(exception);
    }
}

I have confirmed that cookies are being set properly:

_hubConnection = new HubConnectionBuilder()
.WithUrl(uri, opti =>
{
    if (_httpContextAccessor.HttpContext != null)
        foreach (var c in _httpContextAccessor.HttpContext.Request.Cookies)
        {
            opti.Cookies.Add(new Cookie(c.Key, c.Value)
            {
                Domain = uri.Host, // Set the domain of the cookie
                Path = "/" // Set the path of the cookie
            });
        }
})
.Build();

In the program.cs file:

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = IdentityConstants.ApplicationScheme;
    options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
    options.RequireAuthenticatedSignIn = true;
}).AddCookie(options =>
{
    options.LoginPath = "/Account/Login/";
    options.LogoutPath = "/Account/Logout/";
    options.AccessDeniedPath = "/Account/AccessDenied";
    options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
    options.Cookie.HttpOnly = true;
    options.SlidingExpiration = true;
    options.ExpireTimeSpan = TimeSpan.FromSeconds(30);
}).AddIdentityCookies();

app.UseAuthentication();
app.UseAuthorization();

The connection is successful and connected however, the issue I am having is that userEmail in below line is always null:

var userEmail = Context.User?.FindFirst(ClaimTypes.Email)?.Value;

I have found the reason for context being null. I am connecting to hubconnection to send from one server app to another server app. Now for that I have entered URL of the other app which means the user is not authenticated on target URL. When I change url to source it shows context.

Now my question is that how would I authenticate that the user who is sending update to other app is registered user and secure signalr connection as well.

15
  • I haven't tried to use cookie to connect signalr connetion, I prefer using jwt. Commented Jan 8 at 2:27
  • In your scenario, may I know the domain of your 2 application, they should under same domain(a.abc.com,b.abc.com), then they can share cookie between A and B applications. Please share more details, thanks. Commented Jan 8 at 2:30
  • The thing is they can be under same domain and cannot be under same domain so I want it to work in both cases. Currently I am testing (localhost:7120) and (localhost:7244) Commented Jan 8 at 2:52
  • Hi user23341174, you mean it can be working under same domain, right ? Commented Jan 8 at 2:54
  • 1
    Thanks @JasonPan I already implemented something but having some issue. Can you please review link and advise any solution ? Thanks Commented Jan 9 at 2:30

1 Answer 1

1

According to official recommendations, we'd better use Bearer token authentication.

Of course, we can also use Cookie authentication, but we need to pay attention to the browser's restriction that cookies cannot be shared between non-identical domain names.

Here is the sample code for you.

// Retrieve token from localStorage
var token = await _localStorageService.GetItemAsync("jwt_token");


var baseUrl = "https://localhost:7135"; // Replace with your actual domain
var hubPath = "/notificationHub"; // Path to your hub

_hubConnection = new HubConnectionBuilder()
        .WithUrl(new Uri(new Uri(baseUrl), hubPath), options =>
        {
            options.AccessTokenProvider = () => Task.FromResult(token);
        })
        .WithAutomaticReconnect()
        .Build();
Sign up to request clarification or add additional context in comments.

1 Comment

Hi @JasonPan, I am having an issue when logging into one app it logs out of the other application. Do you have any solution for that ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.