Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why new fb api 2.4 returns null email on MVC 5 with Identity and oauth 2?

Everything used to work perfect until fb upgraded it's api to 2.4 (I had 2.3 in my previous project).

Today when I add a new application on fb developers I get it with api 2.4.

The problem: Now I get null email from fb (loginInfo.email = null).

Of course I checked that the user email is in public status on fb profile,

and I went over the loginInfo object but didn't find any other email address.

and I google that but didn't find any answer.

please any help.. I 'm kind of lost..

Thanks,

My original code (which worked on 2.3 api):

In the AccountController.cs:

// // GET: /Account/ExternalLoginCallback [AllowAnonymous] public async Task<ActionResult> ExternalLoginCallback(string returnUrl) {     var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();     if (loginInfo == null)     {         return RedirectToAction("Login");     }     //A way to get fb details about the log-in user:      //var firstNameClaim = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:first_name");  <--worked only on 2.3     //var firstNameClaim = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:name"); <--works on 2.4 api      // 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:             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 });  //<---DOESN'T WORK. loginInfo.Email IS NULL     } } 

In the Startup.Auth.cs:

    Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions fbOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()     {         AppId = System.Configuration.ConfigurationManager.AppSettings.Get("FacebookAppId"),         AppSecret = System.Configuration.ConfigurationManager.AppSettings.Get("FacebookAppSecret"),     };     fbOptions.Scope.Add("email");     fbOptions.Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()     {         OnAuthenticated = (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"));              }             return System.Threading.Tasks.Task.FromResult(0);         }     };     fbOptions.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;     app.UseFacebookAuthentication(fbOptions); 
like image 851
Dudi Avatar asked Aug 17 '15 20:08

Dudi


1 Answers

Taken from a Katana Thread I devised the following:

Change the FacebookAuthenticationOptions to include BackchannelHttpHandler and UserInformationEndpoint as seen below. Make sure to include the names of the fields you want and need for your implementation.

var facebookOptions = new FacebookAuthenticationOptions() {     AppId = "*",     AppSecret = "*",     BackchannelHttpHandler = new FacebookBackChannelHandler(),     UserInformationEndpoint = "https://graph.facebook.com/v2.4/me?fields=id,name,email,first_name,last_name" } 

Then create a custom FacebookBackChannelHandler that will intercept the requests to Facebook and fix the malformed url as needed.

UPDATE: The FacebookBackChannelHandler is updated based on a 27 Mar 2017 update to the FB api.

public class FacebookBackChannelHandler : HttpClientHandler {     protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)     {         if (!request.RequestUri.AbsolutePath.Contains("/oauth"))         {             request.RequestUri = new Uri(request.RequestUri.AbsoluteUri.Replace("?access_token", "&access_token"));         }          var result = await base.SendAsync(request, cancellationToken);         if (!request.RequestUri.AbsolutePath.Contains("/oauth"))         {             return result;         }          var content = await result.Content.ReadAsStringAsync();         var facebookOauthResponse = JsonConvert.DeserializeObject<FacebookOauthResponse>(content);          var outgoingQueryString = HttpUtility.ParseQueryString(string.Empty);         outgoingQueryString.Add("access_token", facebookOauthResponse.access_token);         outgoingQueryString.Add("expires_in", facebookOauthResponse.expires_in + string.Empty);         outgoingQueryString.Add("token_type", facebookOauthResponse.token_type);         var postdata = outgoingQueryString.ToString();          var modifiedResult = new HttpResponseMessage(HttpStatusCode.OK)         {             Content = new StringContent(postdata)         };          return modifiedResult;     } }  public class FacebookOauthResponse {     public string access_token { get; set; }     public string token_type { get; set; }     public int expires_in { get; set; } } 

One useful addition would be to check for the version 3.0.1 of the library and throw an exception if and when it changes. That way you'll know if someone upgrades or updates the NuGet package after a fix for this problem has been released.

(Updated to build, work in C# 5 without new nameof feature)

like image 119
Mike Trionfo Avatar answered Oct 06 '22 01:10

Mike Trionfo