1

I am working on an ASP.NET MVC 5 application which uses External authentication using Google+. I created a project on console.developers.google.com, got the appID and appSecret. Following is the UI and code. I couldn't post images, as I need 10+ reputation :(. My login screen contains a partial view which renders all ExternalLogin Providers. I have registered Google Provider as follows.

 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, user manager and signin manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.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
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });            
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
        app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));

        // Enables the application to remember the second login verification factor such as phone or email.
        // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
        // This is similar to the RememberMe option when you log in.
        app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

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

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

        //app.UseFacebookAuthentication(
        //   appId: "",
        //   appSecret: "");


        app.UseGoogleAuthentication("xxxxxxxxxx_my_appID",
        "xxxxx_my_appsecret");

    }

Code from RouteConfig.cs

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {

        routes.MapRoute(
           name: "signin-google",
           url: "signin-google",
           defaults: new { controller = "Account", action = "ExternalLoginCallback" }
       );
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

    }
}

After clicking the "Sign in with Google" button, the following code is called.

  [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    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 }));
    }

[AllowAnonymous]
    public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
    {
        var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();

        if (loginInfo == null)
        {
            return RedirectToAction("Login");
        }

        //This will fetch the USERPROFILE from google
        if (loginInfo.Login.LoginProvider == "Google")
        {
            var externalIdentity = AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

            var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
            var lastNameClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname);
            var givenNameClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName);
            //var DOBClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.DateOfBirth);
            //var genderClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Gender);
            //var LocClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Locality);
            //var countryClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Country);
            //var mobileClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.);
            var accesstoken = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type.Equals("urn:tokens:google:accesstoken"));
            var Photo = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type.Equals("urn:tokens:google:accesstoken"));
            var userid = loginInfo.ExternalIdentity.GetUserId();

            var email = emailClaim.Value;
            var firstName = givenNameClaim.Value;
            var lastname = lastNameClaim.Value;
            var token = accesstoken.Value;
            Session["FullName"] =   firstName + " " +lastname;
            Session["FName"] = firstName;
            Session["LName"]= lastname;
            Session["Email"] = email;
            Session["GoogleAccessToken"] = token;
            Session["UserID"] = userid;
            //get access token to use in profile image request
           // var accessToken = loginInfo.ExternalIdentity.Claims.Where(c => c.Type.Equals("urn:google:accesstoken")).Select(c => c.Value).FirstOrDefault();
            Uri apiRequestUri = new Uri("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + token);
            //request profile image
            using (var webClient = new System.Net.WebClient())
            {
                var json = webClient.DownloadString(apiRequestUri);
                dynamic result_pic = JsonConvert.DeserializeObject(json);
                Session["PhotoURL"] = result_pic.picture.Value;
            }

        }

        // Sign in the user with this external login provider if the user already has a login
        var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);

        switch (result)
        {
            case SignInStatus.Success:
                {
                    Session["ProviderKey"] = loginInfo.Login.ProviderKey;
                    Session["Provider"] = loginInfo.Login.LoginProvider;
                    return RedirectToLocal(returnUrl);

                }
            case SignInStatus.LockedOut:
                return View("Lockout");
            case SignInStatus.RequiresVerification:
                return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
            case SignInStatus.Failure:
            default:
                // If the user does not have an account, then prompt the user to create an account
                ViewBag.ReturnUrl = returnUrl;
                ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
                return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
        }
    }

The problem is, the ExternalLoginCallback doesn't get called, and the sign-in process is left incomplete. When I used the developer tools to dig deeper into the Request/Response, I see that the Response is Status '302 Found'.

Supporting code as follows,

   internal 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; }

The ChallengeResult() method calls the ExecuteResult(below), post which I get a Google Error: 400-redirect_uri_mismatch

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

I checked these posts and modified code accordingly, but the problem still persists. Why MVC 5 Owin Oauth is not hitting /Account/ExternalLoginCallback action

GoogleOauth2 Issue Getting Internal Server 500 error

Community
  • 1
  • 1
Mihir Kale
  • 1,028
  • 1
  • 12
  • 20
  • 1
    you need to debug it and post a lot more detail.. this question has zero info or code to help you. – Zig Mandel Aug 11 '15 at 14:11
  • Check the console for errors, update your question with more details. Also, just from the sounds of it, you might want to check your authorized origins and redirect URIs to make sure that the URI isn't getting changed for this user. – class Aug 11 '15 at 15:37

0 Answers0