Please only use the issue tracker for bug reports and/or feature requests. For general security questions, or free or commercial support options do __not__ use the issue tracker and instead see here for more details.
For bug reports, include the relevant log files related to your issue. See here how to enable logging. Delete this line once you have.
Finally, please keep the issue concise and to the point. If you paste in more code than the text for the issue you are reporting then we will most likely not read it.
External authentication failing in iOS 12 Safari.
I am seeing failures across all iOS 12 (beta) devices when authenticating using an external provider. There is no issue with iOS <11 or mobile Firefox, Chrome (nor desktop/Android browsers).
var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); is failing for devices running Safari in iOS 12. Tracing the request, it looks like idsrv.external cookie is empty when redirecting from signin-oidc endpoint to the externalcallback endpoint, but is populated if you simply refresh the browser on the externalcallback page.
If you refresh the errored /externalcallback page, it will continue on and work fine.
I understand that iOS 12 is a beta OS, but it is very close to release and it would be nice to get this figured out.
Update: I was able to replicate this in Safari 12.0 for macOS Mojave as well. My guess is this may be more related to Microsoft authentication libraries...
2018-09-07 01:58:39.674 +00:00 [Error] Microsoft.AspNetCore.Server.Kestrel: Connection id "0HLGKCKUC9350", Request id "0HLGKCKUC9350:0000000C": An unhandled exception was thrown by the application.
System.Exception: External authentication error
at IdentityServer.UI.AccountController.ExternalLoginCallback() in C:\Users\Ryan\Projects\Identity Server\src\IdentityServer\UI\Account\AccountController.cs:line 192
at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.Invoke(HttpContext context)
at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at IdentityServer.Startup.<>c.<<Configure>b__6_0>d.MoveNext() in C:\Users\Ryan\Projects\Identity Server\src\IdentityServer\Startup.cs:line 213
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)
/// <summary>
/// Post processing of external authentication
/// </summary>
[HttpGet]
public async Task<IActionResult> ExternalLoginCallback()
{
// read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result.Succeeded != true)
{
throw new Exception("External authentication error");
}
...
}
I have the same issue, but can provide more information.
I don't believe this is a problem with the Identity Server package itself, rather with your application that is hosting the Identity Server package (not your client application). I guess like me you built your Identity Server application using one of the quick start samples. These all use a class called SecurityHeadersAttribute.cs which adds a Content Security Policy (CSP).
I think the upcoming version of Safari either has a bug in it that means it is interpreting this CSP incorrectly and no longer sending cookies with a cross domain POST... or they have implemented a tighter level of security than previously (and tighter than other browsers) which results in the same thing... the correlation and nonce cookies that are set by your client application during authentication are not being sent back to your client application as the user's browser POSTs back from the Identity Server application.
I suspect there are many more Identity Server based applications out there (that are derived from the samples) who will fall foul of this in two weeks time... unless Apple know there is an issue and are about to fix it or the implementer of the Identity Server based application weakens/removes the CSP built into it.
@pierslawson thanks for the insight! I removed my CSP policies from AccountController and ExternalController, but I'm still seeing the issue. I noticed that this problem does not exist with the IdentityServer4 Demo site.
Sounds like an issue with the SameSite cookie attribute. Support for this was just added in Safari 12. https://caniuse.com/#feat=same-site-cookie-attribute. Do you have a Fiddler trace? That would show the SameSite attribute on Set-Cookie headers. We've had to disable SameSite on most OAuth related cookies.
@Tratcher interesting. Any idea how to disable same site on the ExternalCookieAuthenticationScheme? Am I best off just creating a custom scheme?
Looks like bullet dodged on potential login failures starting next week.
This did it for me:
.AddCookie(Constants.PingExternalAuthenticationScheme, options =>
{
options.Cookie.Name = Constants.PingExternalAuthenticationScheme;
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
})
Ah, services.ConfigureExternalCookie(...); for folks using ASP.NET Identity.
@Tratcher would this be considered a bug in Safari 12's implementation of SameSite cookies? I used their Feedback app to report the discrepancy in behavior; not sure if there is a better route.
None of the browsers have quite decided how to handle OAuth scenarios with SameSite. When we tested them a few months ago they all broke and we had to disable SameSite. I hear a few of them now work the the Lax setting but apparently not IOS.
Got it... so disabling SameSite completely is the best option for now. Thanks for your background on the issue!
@brockallen , it looks like the idsrv.external cookie options in AddIdentityServer cannot be overridden, or I can't figure out how. This cookie is being assigned the samesite=lax setting. This setting breaks in iOS 12. Any direction on how to change this?
I need to add options.Cookie.SameSite = SameSiteMode.None;
to ConfigureInternalCookieOptions Line 41
if (name == IdentityServerConstants.ExternalCookieAuthenticationScheme)
{
///need options.Cookie.SameSite = SameSiteMode.None; here
options.Cookie.Name = IdentityServerConstants.ExternalCookieAuthenticationScheme;
}
I have already modified all other cookies, but can't seem to figure out how to modify this one. This is what I have done.
service.ConfigureApplicationCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.None;
});
service.ConfigureExternalCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.None;
});
...
app.UseCookiePolicy(new CookiePolicyOptions {MinimumSameSitePolicy = SameSiteMode.None});
...
//for each of my external login providers
options.CorrelationCookie.SameSite = SameSiteMode.None;
You don't have to use our external cookie. As such, configure your own and make your UI use it (instead of ours). Having said that, I will fix ours in the next release.
Thanks again for the great tools @brockallen and for supporting the community.
When is this fix set to release @brockallen ?
We will be working on the next preview late next week.
Okay, thanks for answering. :)
I can comfirm creating our own cookie scheme, without SameSite solved all iOS 12 related problems here as well.
Running into this problem as well using Azure AD for authentication.
@NickyM can you please post some code on what you had to do to get your own cookie scheme working? Thanks!
@dpotoole, well basically I just renamed the cookie, changed SameSite to SameSiteMode.None, and then signed in using the renamed cookie.
I have two instances of IdentityServer, where one is using AspNetIdentity and one doesnt.
Startup.cs:
services.AddAuthentication(".YourCookieScheme.Name")
.AddCookie(".YourCookieScheme.Name", options =>
{
options.Cookie.Name = ".YourCookieScheme.Name";
options.Cookie.SameSite = SameSiteMode.None;
})
.AddMicrosoftAccount(options =>
{
options.CorrelationCookie.SameSite = SameSiteMode.None;
options.SignInScheme = ".YourCookieScheme.Name";
//Rest of settings here
})
.AddGoogle(options =>
{
options.CorrelationCookie.SameSite = SameSiteMode.None;
options.SignInScheme = ".YourCookieScheme.Name";
//Rest of settings here
});
And in /Account/ExternalLoginCallback:
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
var info = await GetExternalLoginInfoAsync();
//( ... irellevant business logic ... )
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
//( ... irellevant business logic ... )
await _httpContextAccessor.HttpContext.SignOutAsync(".YourCookieScheme.Cookies");
//( ... irellevant business logic ... )
}
Where GetExternalLoginInfoAsync() is a modified copy of _sigInManager.GetExternalLoginInfo (https://github.com/aspnet/Identity/blob/master/src/Identity/SignInManager.cs)
private async Task<ExternalLoginInfo> GetExternalLoginInfoAsync(string expectedXsrf = null)
{
var auth = await _httpContextAccessor.HttpContext.AuthenticateAsync(".YourCookieScheme.Name");
//rest of method
}
private async Task<IEnumerable<AuthenticationScheme>> GetExternalAuthenticationSchemesAsync()
{
var schemes = await _schemes.GetAllSchemesAsync();
return schemes.Where(s => !string.IsNullOrEmpty(s.DisplayName));
}
Startup.cs:
services.AddAuthentication(".AnotherCookieScheme.Name")
.AddCookie(".AnotherCookieScheme.Name", options =>
{
options.Cookie.Name = ".AnotherCookieScheme.Name";
options.Cookie.SameSite = SameSiteMode.None;
})
.AddOpenIdConnect(options =>
{
options.CorrelationCookie.SameSite = SameSiteMode.None;
options.SignInScheme = ".AnotherCookieScheme.Name";
//Rest of settings here
})
Account/ExternalLoginCallback:
public async Task<IActionResult> ExternalCallback(string returnUrl)
{
//Read external identity from the tempoary cookie
var result = await HttpContext.AuthenticateAsync(".AnotherCookieScheme.Cookies");
//(.... extracting claims from result and signing in ... )
// Delete tempoary cookie used during external authentication
await HttpContext.SignOutAsync(".AnotherCookieScheme.Cookies");
}
Hope that it helps. I have no clue, if this is the best and most optimal solution, but it works and I'm happy.
After looking more closely at what IdentityServer was doing, I figure out a workaround. This has been confirmed on our system. My workaround just targets this cookie:
public class FixIos12Cookies : IConfigureNamedOptions<CookieAuthenticationOptions>
{
public void Configure(CookieAuthenticationOptions options)
{
}
public void Configure(string name, CookieAuthenticationOptions options)
{
if (name == IdentityServerConstants.ExternalCookieAuthenticationScheme)
{
options.Cookie.SameSite = SameSiteMode.None;
}
}
}
Then after you setup IdentityServer, you add this to DI
var idServerBuilder = services.AddIdentityServer();
...
services.AddSingleton<IConfigureOptions<CookieAuthenticationOptions>, FixIos12Cookies>();
This should be able to be removed when the patch goes through.
I tried the workaround above and it didn't work. But keep reading if it didn't work for you either. I then tried the solution described here: https://github.com/aspnet/Identity/issues/1970#issuecomment-424566071 which did work.
Then I thought that this option may work after all, but the singleton needs to be registered AFTER services.AddAuthentication().
Please correct me if I'm wrong @cdwaddell :)
_Deeper information below_
I've kept reading on as I wanted to know what this SameSite cookie option was and why it was broken in iOS 12, which led me to the following bug reported to Webkit: https://bugs.webkit.org/show_bug.cgi?id=188165
And that led me to https://datatracker.ietf.org/doc/draft-ietf-httpbis-rfc6265bis/ which is an Expired Internet Draft.
As far as I can see, Webkit implemented an expired draft, which is probably expired for a reason (seeing it doesn't work with oAuth).
@Devvox93 , you do have to combine it with a couple of other settings changes that I had posted earlier. The most important is changing the MinimumSameSitePolicy to allow the SameSiteMode.None. Otherwise the same site setting may not get applied because it is too low. Here they all are in one place.
service.ConfigureApplicationCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.None;
});
service.ConfigureExternalCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.None;
});
...
app.UseCookiePolicy(new CookiePolicyOptions {MinimumSameSitePolicy = SameSiteMode.None});
...
//for each of your external login providers
options.CorrelationCookie.SameSite = SameSiteMode.None;
If it still doesn't work for you, then you have another cookie with the bad policy. The way I tracked them all down was inspecting the cookies in Chrome's Developer Tools. I had to inspect each response for new cookies being added. Each cookie needed it's own samesite policy change. After the changes above, the external cookie itself still had the issue. That was what caused the need for the workaround:
public class FixIos12Cookies : IConfigureNamedOptions<CookieAuthenticationOptions>
{
public void Configure(CookieAuthenticationOptions options)
{
}
public void Configure(string name, CookieAuthenticationOptions options)
{
if (name == IdentityServerConstants.ExternalCookieAuthenticationScheme)
{
options.Cookie.SameSite = SameSiteMode.None;
}
}
}
Then add it to DI. The only requirement (I think) is for it to be after AddIdentityServer:
var idServerBuilder = services.AddIdentityServer();
...
services.AddSingleton<IConfigureOptions<CookieAuthenticationOptions>, FixIos12Cookies>();
In my case, this (and only this) was what I did to fix the issue:
services.AddAuthentication()
.Services.ConfigureExternalCookie(options =>
{
options.Cookie = new CookieBuilder
{
SameSite = SameSiteMode.None
};
});
EDIT: This was done as the final step in ConfigureServices.
I see @NickyM also setting the None setting on the correlation cookies for the external providers. Can anyone confirm if that is also necessary for this iOS 12 browser issue? I did not see that as necessary from the Microsoft announcement.
@brockallen: I'll double check tomorrow morning. It's 10:15pm here, and I'll have to get to my workplace to run and iOS device agains my development machine.
Better have as little disabled as possible. Haven't tried the ConfigureExternalCookie. ConfigureApplicationCookie alone isn't enough, and I did unfortunately not spot that one at the time of patching.
I've made the change for the external cookie (also done in the ASP.NET Identity integration library as well).
I'll wait to hear back from @NickyM on the others (but I don't expect we will need to, as those cookies are lax and the redirect to the external providers are typically in the top browser window and often use GET). We might need them for the OIDC/WS-Fed providers though, come to think of it, since they send results back via POST (and thus the same issue would arise).
Ok, good to hear. Thanks for the info @Tratcher.
Given that info, I can close this. Thanks all.
@brockallen any idea when this will be released? I can implement one of these workarounds but if it's going to be pushed out this week I'll just wait. Thanks!
preview2 will be in the next week. rtm end of next month (november).
I will give preview 2 a try then. thanks!
Version 2.2.0-preview3-35497 did not solve the problem still signInManager.GetExternalLoginInfoAsync() returning null but it is working fine on windows an earlier version ios
Hi @leastprivilege , have the same issue (I think) in Safari for windows. Have upgraded to 2.3.0 and can see that the external cookies now gone from LAX to None. Any ideas?
does this mean that updating my identityserver to 2.3.0 would not need these workarounds? should there be any codes changes in the web client app as well? Thanks!
@prflores updating to 2.3 fixed this issue for me
Thanks for the info @rushfrisby. I am not sure if this is related to my issue but I'm experiencing an infinite redirection between my identityserver and client site from iOS 12 users only (android, windows desktop and old version of iOS is fine) when signing-in (identity server with asp.net identity). I upgraded to 2.3.0 but still same result. I compared the logs got from iOS users and desktop users and surprised it is almost the same. I'm not sure where to start checking. Any help would do. Thank you.
FYI, here's a different approach to solving this issue, while being able to use same-site cookies.
Maybe someone can test on Safari and iOS for me?
Thanks! This is a very helpful analysis of the root cause. I had considered
a similar approach to the general siolution but gave up when the sign-out
function was directly using cross site, thinking that I wouldn鈥檛 be able to
find a way around that. I鈥檒l give it a try.
鈥擠aniel
Thanks @brockallen! this approach fixed my issue.
The latest approach from @brockallen worked for me too - as a point of interest I only started experiencing this issue after adding
services.AddOidcStateDataFormatterCache("oidc");
in order to fix an intermittent problem I was seeing with "Correlation failed" errors that I believe was related to the state sometimes being too large. Before that iOS/Safari login was working fine.
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Most helpful comment
In my case, this (and only this) was what I did to fix the issue:
EDIT: This was done as the final step in ConfigureServices.