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