-
Notifications
You must be signed in to change notification settings - Fork 871
When exception thrown in ExternalCallbackConfirmation the External cookie is corrupted and all the following requests are redirected to /Account/AccessDenied #915
Comments
Are you seeing this in 1.0.0 and not an older build like RC2? |
@HaoK, correct I'm using 1.0.0. Let me know if you want. |
@HaoK Any temporary workarounds? |
You can always clear the external cookie explicitly via SignOut with a try catch around ExternalCallbackConfirmation |
I have some trouble in my application on 1.0.0 and in fresh WebApplication template with oAuth. I see in logs:
and redirect to /Account/AccessDenied I tried add await SignInManager.SignOutAsync(); to ExternalLogin methods but it not works. |
@kroniak I have the same issue, one thing you might consider using is adding an It's a nasty workaround though, I'm waiting for a decent fix too. |
@gdoron In my situation
After first time was happen there are no successful login with oauth. I can delete Identity.External Cookie but is a bug. |
Just to clarify the purpose of the external cookie, its there to store the identity/claims from another auth provider (i.e. google/facebook/OAuth). Identity will pick up and use that cookie mostly just to link an external login. If your app ends up in a state where you got a 'bad' external cookie, there is no other recourse other than clearing it explicitly (or waiting for it to time out on its own). That said, why do you think the external cookie is 'corrupted'? |
@kroniak SignOutAsync should be deleting the external cookie |
Because unless I clear it, That doesn't seem like an expected behavior, right? |
cc @Tratcher this new behavior where the presence of the "external" cookie causing an immediate forbidden is a side effect of the new RemoteAuthHandler.Authenticate => SignInScheme delegation behavior change we made. In the new world with this behavior, the presence of any external cookie, will prevent challenges from any other social auth provider, since they will result in Forbiddens as long as the cookie is there since its the 'SignInScheme" |
@HaoK Cookie is not deleting in some sceneries. It is in my situation on base template:
Stop, how can we logout if we are not logged?? OK, add simple Log:
|
The Identity+OAuth login flow is a multi-step process that always assumes it's starting from scratch. This assumption causes issues when it gets to the external login step if the is already an external identity. The external identity is probably valid, the flow is just not set up to account for its presence. There are at least three places in the login flow where an external identity could be correctly accounted for.
Option 1 is the cleanest, but 2 would also work for most scenarios. 3 has some complexities we'd rather not deal with if we can avoid them. |
@Tratcher Yes, first option is clear. To options 2: what happens if user decides to use other provider at the middle of a process? Option 1 wins. |
|
@Tratcher I see in
Is it not equv with In logs I see
The cookie was not deleted. If I am using suggested method: |
Those sign out calls are equivalent assuming you pass in the right AuthenticationScheme for the external cookie, is more stuff happening in the request after your call to SignOut? |
@HaoK look at #915 (comment) I wrote. Nothing changed. You can reproduce this issue in my repo. |
Why is there a call to Executing ChallengeResult with authentication schemes (Google). in your log after the signouts? |
@HaoK because |
@HaoK I can send a whole log. |
You have to sign out, do a redirect to clear the cookie, and then start the login. You need to send a response that clears out the cookie, so you can't sign out/sign in in the same request |
@HaoK Where should I make a redirect? |
Redirect to an action that signs out and bounces back to external login |
Why not implement option 1 above where you call signout when loading the Login page? |
@Tratcher It is not right. If user get /login page is not eq user wants to logout. It works by: [HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginLogOff(string provider, string returnUrl = null)
{
await _signInManager.SignOutAsync();
_logger.LogInformation(4, "User logged out.");
return RedirectToAction("ExternalLogin", "Account", new { provider, returnUrl });
}
[HttpGet]
[AllowAnonymous]
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
} |
Do I replace External with Google etc? |
@mikes-gh no, it needs to be the auth scheme from your CookieAuthenticationOptions that stores your short term login-cookie. If you're using Identity then it will be "External". |
Hmm that doesn't work for me. |
I do appreciate everyone's work on this but I'm feeling a bit short changed. |
@mikes-gh Since they decided the fix should be in the templates, you can simply ignore the fix, and change your code as you wish. |
@gdoron |
@mikes-gh when we tested this we found it took deliberate effort to get to a cached version of the login page. The most obvious way was to click back, but you'd have to do it multiple times with the right timing to get past the external provider redirects. When reviewing option 3 we found it added complexity and opened additional security concerns around anti-forgery. In the end we opted for a simple solution that addressed the most reproducible scenarios. We also plan to address aspnet/Security#859 which should prevent this issue when switching providers. If this continues to be a common problem for users we'll see what else can be done about it. |
@Tratcher well I still don't know what should be the fix in my scenario where the external login isn't issues from the Login action.
Users not being able to login and practically leaving the website and never return is severe enough IMHO, that fix should fix common and uncommon scenarios alike. |
@gdoron it would help if you shared a sample app showing your usage scenario. It's unclear how significantly it diverges from the template. |
@Tratcher It doesn't diverge too much. Basically we took the login View template, added some CSS and stuff and backed it in a partial view, and it's loaded in the layout.cshtml
The server template code wasn't changed too much (we don't use the ExternalLoginConfirmation to allow users pick their email/username after the oauth but create the user inside the ExternalLoginCallback) I'm adding you as a collaborator to the real full private repository in case you want to see it exactly. |
Almost forgot, I really appreciate the effort @Tratcher! |
BTW when I said people can't login is a huge deal, it's a huge deal for every website, losing users in the most important phase of the registration. |
That's a curious pattern you have there. It looks like you render your login section and register section into every page. This is half-way to a SPA app. I don't see a fool-proof option for you. You're already using Option 3 as described above, and that should be adequate unless the user wants to switch providers part way through login. You could facilitate provider switching by adding a "Start Over / Switch Provider" button to your ExternalLoginCallback view that pointed to the LogOff endpoint. |
Thanks for taking a look Chris. If I'll stop render the login/register on every page and just load it on demand as a partial view, and in the action that renders these views I'll signout the user. This is what Mike and I said about the problem with the suggested fix, it doesn't fix existing applications which might have a bit different flow (except the cache page issue that Mike was concerned about). Thanks Chris! |
Yes, that would be in line with what we now do in the templates. |
@Tratcher |
@mikes-gh Try adding Do you still get a cached copy? |
@mikes-gh Did it help? |
@gdoron |
I would like to make a motion to strike my comment from the record. After more research, I believe my issue with Auth0 (still unresolved after more than a month) is specific to Auth0 and has nothing to do with any of Microsoft's code |
Is the final suggested solution to this problem "removing the cookie"? Please checkout my project I might have to create my own encrypted cookie to store the external login info so that I can retrieve across different requests. |
I absolutely agree with @gdoron here. I have built my entire boilerplate (here)(https://github.com/pauldotknopf/react-aspnet-boilerplate) around this feature behaving as it did in RC2, and now it is completely broken. Sure, you fixed it in the templates, but my workflow is completely different for the external logins and your fix in the templates doesn't translate to my boilerplate. This is incredibly frustrating. Where is the source of the problem? I will fork and fix it. |
Though it was discussed in length already, I just want to add one more thing. I used @mikes-gh 's code, so I'm sort of safe, but other people which surely 99.99% of them didn't track this thread are not safe. Our homepage in case someone wants to better understand what I'm talking about: |
The thing is, the template fix is good as long as you use the exact same flow, pages and code for login/register as in the template. |
@gdoron I still believe this is an issue, but I have a workaround that will fix your problem. Instead if redirecting (to flush cookie) before they get to the external login redirect, do the flushing right at the external login redirect. For example: [Route("externalloginredirect")]
public async Task<IActionResult> ExternalLoginRedirect(string provider, bool autoLogin = true, bool didRefresh = false)
{
// when we first visit this redirect page, we need delete any previous authentication tokens.
// See https://github.com/aspnet/Security/issues/299
if (!didRefresh)
{
// redirec the user to this same exact page, but clear out any external login cookie that may be there.
// initial requests to this page should not have the "didRefresh" set.
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
var queryString = new QueryString(Request.QueryString.ToString());
return Redirect(Request.Path + queryString.Add("didRefresh", "true"));
}
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, "/externallogincallback?autoLogin=" + autoLogin);
return new ChallengeResult(provider, properties);
} |
@pauldotknopf Looks good to me. |
The |
I just installed Visual Studio 2017 and ran the authentication Template. PROBLEM NOT SOLVED - If you do not follow the assumed sequence of operations when registering you very quickly ends up with Account/AccessDenied. This is totally unacceptable. Being able to always log in should be the most basic requirement of a web site!... PLEASE HELP! DO ANYONE HAVE A SOLUTION TO THE Account/AccessDenied PROBLEM? |
Thanks @pauldotknopf
UPD1: Not working - if I approach, that I described - I can't post externalLoginConfirmation |
Found it because of #914.
The text was updated successfully, but these errors were encountered: