aspnet / Templates

This repo is OBSOLETE - please see the README file for information
Other
150 stars 57 forks source link

Fix external login flow for failed/partial authentication #660

Closed Eilon closed 8 years ago

Eilon commented 8 years ago

Original bug: https://github.com/aspnet/Identity/issues/915

We want to make the change described in option (3) in @Tratcher 's comment at https://github.com/aspnet/Identity/issues/915#issuecomment-238939350.

Specifically, if the user attempts to use an OAuth login and they still have the external login cookie, the template code will automatically pick up where it left off and complete the flow.

Eilon commented 8 years ago

We can have @troydai do the fix to the templates.

mikes-gh commented 8 years ago

We need to account for Identity.External being a different provider. @Tratcher solution didn't allow for this. TODO : refactor out repeated code

// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ExternalLogin(string provider, string returnUrl = null)
{
    var info = await _signInManager.GetExternalLoginInfoAsync();

    // No External Cookie (normal flow)
    if (info == null)
    {
        var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
        var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
        return new ChallengeResult(provider, properties);
    }
    else
    {

        // External Cookie present same provider
        if (info.LoginProvider == provider)
        {
            return RedirectToAction(nameof(AccountController.ExternalLoginCallback), "Account");
        }

        // External Cookie present different provider
        else
        {
            await _signInManager.SignOutAsync();
            return RedirectToAction(nameof(AccountController.ExternalLoginRedirect), "Account", new { provider = provider, returnUrl = returnUrl });
        }
    }   
}

[HttpGet]
[AllowAnonymous]
public IActionResult ExternalLoginRedirect(string provider, string returnUrl = null)
{
    var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
    var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
    return new ChallengeResult(provider, properties);
}
troydai commented 8 years ago

Thanks, @mikes-gh . A good point. I'll make sure different provider scenario is handled.

Tratcher commented 8 years ago

RE: Challenging for provider A does not currently work if you're already logged in with provider B and they share the same cookie: https://github.com/aspnet/Security/issues/859

troydai commented 8 years ago

By the way, I'll also take a loot into issue https://github.com/aspnet/Security/issues/859 as well. Challenging the auth if the provider is changed can be another approach.

troydai commented 8 years ago

Prototype v1

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> ExternalLogin(string provider, string returnUrl = null)
        {
            var info = await _signInManager.GetExternalLoginInfoAsync();
            if (info != null)
            {
                if (string.Equals(info.LoginProvider, provider))
                {
                    // There was a successful external log in from the same log in provider.
                    // Skip challenging and redirect to next step.
                    return RedirectToAction(nameof(ExternalLoginCallback));
                }
                else
                {
                    // There was a successful external log in from a different login provider.
                    // Sign out from the external log in provider
                    await _signInManager.SignOutAsync();

                    return RedirectToAction(
                        nameof(ExternalLoginChallenge), "Account",
                        new { provider = provider, returnUrl = returnUrl });
                }
            }

            return ExternalLoginChallenge(provider, returnUrl);
        }

        [HttpGet]
        [AllowAnonymous]
        public IActionResult ExternalLoginChallenge(string provider, string returnUrl = null)
        {
            var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
            var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
            return Challenge(properties, provider);
        }
troydai commented 8 years ago

/cc @HaoK

Tratcher commented 8 years ago

Open questions:

HaoK commented 8 years ago

My feeling is still that we should add a switch to turn off the new behavior we added instead of forcing this extra redirect, the flow is complicated enough without an additional redirect.

The AuthenticateUsingSignInScheme behavior is totally not desired in the multiple shared sign in cookie scenario, if we added an opt-out, then we'd only need to update the documentation when adding ExternalLogins to include setting this false.

We can actually already detect this multiple shared sign in scheme scenario already. If the Shared SignInScheme is set (which identity does by default) https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNetCore.Authentication/SharedAuthenticationOptions.cs

An alternative clean fix then could be to add the new AuthenticateUsingSignInScheme flag to SharedAuthenticationOptions as a bool?, update the RemoteMiddleware to also consume the new shared setting and then identity could just turn this off as part of its default setup. Existing apps could also opt out of this behavior by flipping that to off as well.