I'm trying to setup integrated OWIN Facebook authentication in a new MVC 5 project in Visual Studio 2013. I have configured apps and keys as per this tutorial:
http://www.asp.net/mvc/tutorials/mvc-5/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on
However, I'm getting a NullReferenceException thrown from this call in the AccountController:
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
I already checked the response in Fiddler and am getting what appears to be a success response from Facebook, but still get this error. The response looks like this:
{"id":"xxx","name":"xxx","first_name":"xxx","last_name":"xxx","link":
"https:\/\/www.facebook.com\/profile.php?id=xxx","location":{"id":"xxx","name":"xxx"},
"gender":"xxx","timezone":1,"locale":"en_GB","verified":true,"updated_time":"2013-10-23T10:42:23+0000"}
I get this when debugging in http as well as https. I'm guessing this is a framework bug but have so far drawn a blank diagnosing this through reflector.
This probably is a bug in identity OWIN extension code. I can't repro the issue as my facebook payload always returns a username field in json, which is missing from your fb response. I am not quite sure why it's not there.
The code in identity owin extension method doesn't have a null check for the identity's name claim which is same as the username field. We have filed a bug for it internally.
In order to workaround this issue, could you try replacing your ExternalLoginCallback method with following code:
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var result = await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie);
if (result == null || result.Identity == null)
{
return RedirectToAction("Login");
}
var idClaim = result.Identity.FindFirst(ClaimTypes.NameIdentifier);
if (idClaim == null)
{
return RedirectToAction("Login");
}
var login = new UserLoginInfo(idClaim.Issuer, idClaim.Value);
var name = result.Identity.Name == null ? "" : result.Identity.Name.Replace(" ", "");
// Sign in the user with this external login provider if the user already has a login
var user = await UserManager.FindAsync(login);
if (user != null)
{
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
else
{
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { UserName = name });
}
}
The code will set default user name as empty when there is no username back from facebook/google.
Hongye Sun did all the heavy lifting in his answer above.
Here's some code that can be added to your controller class and be called in place of the troublesome AuthenticationManager.GetExternalLoginInfoAsync().
private async Task<ExternalLoginInfo> AuthenticationManager_GetExternalLoginInfoAsync_Workaround()
{
ExternalLoginInfo loginInfo = null;
var result = await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie);
if (result != null && result.Identity != null)
{
var idClaim = result.Identity.FindFirst(ClaimTypes.NameIdentifier);
if (idClaim != null)
{
loginInfo = new ExternalLoginInfo()
{
DefaultUserName = result.Identity.Name == null ? "" : result.Identity.Name.Replace(" ", ""),
Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value)
};
}
}
return loginInfo;
}
I had the same problem. I solve my problem just added app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
to the Startup.Auth.cs. I didn't have that in my Startup.Auth.cs so
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
always thrown me an Object reference not set to an instance of an object error. I figure that out by analyzing VS 2013 default template for MVC 5. So if you need more info on code structure or example take a look on VS 2013 MVC5 template.
I came across this post a few days ago but unfortunately none of the above solutions worked for me. so here is how I managed to fix it and get the email from Facebook.
Microsoft.Owin
to version 3.1.0-rc1
Microsoft.Owin.Security
to version 3.1.0-rc1
Microsoft.Owin.Security.Cookies
to version 3.1.0-rc1
Microsoft.Owin.Security.OAuth
to version 3.1.0-rc1
Microsoft.Owin.Security.Facebook
to version 3.1.0-rc1
Then add the following code to the Identity Startup
class
var facebookOptions = new FacebookAuthenticationOptions()
{
AppId = "your app id",
AppSecret = "your app secret",
BackchannelHttpHandler = new FacebookBackChannelHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.8/me?fields=id,name,email,first_name,last_name",
Scope = { "email" }
};
app.UseFacebookAuthentication(facebookOptions);
This is the definition class for FacebookBackChannelHandler()
:
using System;
using System.Net.Http;
public class FacebookBackChannelHandler : HttpClientHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
System.Threading.CancellationToken cancellationToken)
{
// Replace the RequestUri so it's not malformed
if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
request.RequestUri = new Uri(request.RequestUri.AbsoluteUri.Replace("?access_token", "&access_token"));
}
return await base.SendAsync(request, cancellationToken);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With