I'm using signalr to implement a chat service. On form submission in the chat it hits a controller that authenticates the user and adds a cookie that I then use to keep the chat current via signalr. This works perfectly on all browsers except for the ones running on IOS. The authentication is done to a different domain to that the chat window is displaying on.
When I inspect the network tab and compare, I see the "aspnetcore.cookies" response to the authentication post in the browsers that work but not on IOS.
I have tried various settings in start up and this is what is currently in place
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.SlidingExpiration = true;
options.LoginPath = "/Identity/Account/Login"; // If the LoginPath is not set here, ASP.NET Core will default to /Account/Login
options.LogoutPath = "/Identity/Account/Logout"; // If the LogoutPath is not set here, ASP.NET Core will default to /Account/Logout
options.AccessDeniedPath = "/Identity/Account/AccessDenied"; // If the AccessDeniedPath is not set here, ASP.NET Core will default to /A
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.IsEssential = true;
});
services.AddSession(
options =>
{
options.Cookie.SameSite = SameSiteMode.None;
});
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
});
services.AddSignalR(hubOptions =>
{
hubOptions.EnableDetailedErrors = true;
});
app.UseRouting();
app.UseCookiePolicy(new CookiePolicyOptions
{
MinimumSameSitePolicy = SameSiteMode.None
});
app.UseAuthentication();
app.UseAuthorization();
So this turned out to be IOS browsers not playing nicely with authentication cookies from a different domain. I was able to verify this by turning off the setting in Safari settings that prevent tracking cookies.
To work around this I found two ways. Switch to JWT authentication and then pass the token in the connection action. I saved the token in local storage and then would pass it on each call. Something like this :
fetch('/api/account/login', {
method: 'POST',
body: JSON.stringify({
username: 'username',
password: 'password'
}),
headers: {
'Content-Type': 'application/json'
}}).then(response => response.json()).then(data => {
localStorage.setItem('token', data.token);}).catch((error) => {
console.error('Error:', error);});
I would then use this token in the connection action as follows:
var connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub", { accessTokenFactory: () => localStorage.getItem('token') })
.build();
How secure this is I can't attend to but thought I would post this in case it helps someone. I ended up going a different way where I removed authentication altogether. But my use case is unique as I am able to control the authentication on the site the chat service is hosted on.
I hope this helps someone.