Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IAuthenticationManager.Challenge not calling ExternalLoginCallback

I'm having issues with getting Social login to work in our existing ASP.NET MVC website project. The normal customer (our custom DB) login works just fine. For some reason the Challenge method on the IAuthenticationManager is not redirecting to the ExternalLoginCallback Action so that the proper Social login provider can prompt for login. Right now the Challenge method is redirecting back to the AccountController Login Action and the url after the login page loads looks like this:

http://localhost/Account/Login?ReturnUrl=%2fAccount%2fExternalLogin

I have went through multiple tutorials on the ASP.Net site that pertains to the new Identity API. I went through this tutorial first to get an understanding on the code, setup, and to create a Proof of Concept. I then went along with this tutorial to meld the new Identity API into our existing web site and replace our old System.Web.Security.MembershipProvider implementation. Here are some snapshots of the code.

Startup.Auth.cs

public partial class Startup
{
    // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context and user manager to use a single instance per request
        //app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity = SecurityStampValidator
                                    .OnValidateIdentity<ApplicationUserManager, IdentityUser, int>(validateInterval: TimeSpan.FromMinutes(30), 
                                                                                                    regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager), 
                                                                                                    getUserIdCallback: (id) => (Int32.Parse(id.GetUserId())))
            }
        });

        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // Uncomment the following lines to enable logging in with third party login providers
        //app.UseMicrosoftAccountAuthentication(
        //    clientId: "",
        //    clientSecret: "");

        //app.UseTwitterAuthentication(
        //   consumerKey: "",
        //   consumerSecret: "");

        FacebookAuthenticationOptions fbAuthenticationOptions = new FacebookAuthenticationOptions();
        fbAuthenticationOptions.Scope.Add("email");
        fbAuthenticationOptions.AppId = "XXXXXX";
        fbAuthenticationOptions.AppSecret = "YYYYYYY";
        fbAuthenticationOptions.Provider = new FacebookAuthenticationProvider()
                                           {
                                               OnAuthenticated = async context =>
                                               {
                                                   context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
                                                   foreach (var claim in context.User)
                                                   {
                                                       var claimType = string.Format("urn:facebook:{0}", claim.Key);
                                                       string claimValue = claim.Value.ToString();
                                                       if (!context.Identity.HasClaim(claimType, claimValue))
                                                           context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, "XmlSchemaString", "Facebook"));

                                                   }

                                               }
                                           };
        fbAuthenticationOptions.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
        app.UseFacebookAuthentication(fbAuthenticationOptions);

        //app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
        //{
        //    ClientId = "",
        //    ClientSecret = ""
        //});
    }
}

ChallengeResult class inside of AccountController.cs

private class ChallengeResult : HttpUnauthorizedResult
{
    public ChallengeResult(string provider, string redirectUri)
        : this(provider, redirectUri, null)
    {
    }

    public ChallengeResult(string provider, string redirectUri, string userId)
    {
        LoginProvider = provider;
        RedirectUri = redirectUri;
        UserId = userId;
    }

    public string LoginProvider { get; set; }
    public string RedirectUri { get; set; }
    public string UserId { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
        if (UserId != null)
        {
            properties.Dictionary[XsrfKey] = UserId;
        }

        IOwinContext owinContext = context.HttpContext.GetOwinContext();
        IAuthenticationManager authenticationManager = owinContext.Authentication;
        try
        {
            authenticationManager.Challenge(properties, LoginProvider);
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

ExternalLogin in AccountController.cs

public ActionResult ExternalLogin(string provider, string returnUrl)
{
    // Request a redirect to the external login provider
    return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}

ExternalLoginCallBack in AccountController.cs

public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
    var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
    if (loginInfo == null)
    {
        return RedirectToAction("Login");
    }

    // Sign in the user with this external login provider if the user already has a login
    var user = await UserManager.FindAsync(loginInfo.Login);
    if (user != null)
    {
        await SignInAsync(user, isPersistent: false);
        return RedirectToLocal(returnUrl);
    }
    else
    {
        // Get the information about the user from the external login provider
        var info = await AuthenticationManager.GetExternalLoginInfoAsync();
        if (info == null)
        {
            return View("ExternalLoginFailure");
        }
        string email = info.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:email").Value;
        string firstName = info.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:first_name").Value;
        string lastName = info.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:last_name").Value;

        // If the user does not have an account, then prompt the user to create an account
        RegisterDisplay registerDisplay = new RegisterDisplay
                                          {
                                              Email = email,
                                              Agree = true,
                                              UserName = loginInfo.DefaultUserName,
                                              MailingAddress = new MailingAddress() { FirstName = firstName, LastName = lastName }
                                          };

        ViewBag.ReturnUrl = returnUrl;
        ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
        TempData["RegisterDisplay"] = registerDisplay;
        return View("Register", returnUrl);
    }
}

This one has me stumped as I'm not seeing any errors being thrown in the debugger. Please let me know if there is any other code that needs to be shown. Any help would be greatly appreciated. Thank you.

like image 776
Jimbo Avatar asked Sep 24 '14 18:09

Jimbo


2 Answers

In my case, the challenge was simply returnung 401 (Unauthorized):

HttpContext.GetOwinContext().Authentication.Challenge("Application")

To get it working, I had to change it to:

HttpContext.GetOwinContext().Authentication.Challenge(DefaultAuthenticationTypes.ApplicationCookie)

Because this is how I configured my Cookie authentication on Startup:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, // <---- I have to pass the same value as `AuthenticationType` to the `Challenge` method
    AuthenticationMode = AuthenticationMode.Passive,
    LoginPath = new PathString("/Account/Login"),
    Provider = cookieAuthenticationProvider
});

The Challenge method works only with registered authentication methods and it reconizes them via the configured AuthenticationType property I you can se above.

like image 25
Machado Avatar answered Nov 14 '22 21:11

Machado


Ok a co-worker and I figured out the issue with the Challenge method skipping the ExternalLoginCallback. This was a web.config issue, which I forgot to post with my original question. We needed to modify the authentication mode to be None. It use to be Forms, which was causing the site to hijack the Challenge call.

Original system.web section in the web.config

<system.web>
    <httpRuntime targetFramework="4.5" />
    <compilation debug="true" targetFramework="4.5" />
    <authentication mode="Forms">
        <forms loginUrl="~/Account/Login" timeout="2880" />
    </authentication>
</system.web>

Fixed system.web section in the web.config

<system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
</system.web>

We also had to add a remove child to the system.webServer modules section

<system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="FormsAuthenticationModule" />
    </modules>
</system.webServer>

Now everything is redirecting as it should.

like image 78
Jimbo Avatar answered Nov 14 '22 23:11

Jimbo