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.
a.abc.com,b.abc.com), then they can share cookie between A and B applications. Please share more details, thanks.