27

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.

Liam
  • 27,717
  • 28
  • 128
  • 190
Gracie
  • 612
  • 1
  • 6
  • 13
  • 1
    In case anyone else stumbles upon this, there's a now a nuget update that takes care of the problem. Just update your existing nuget packages in your project. – stroebele May 10 '14 at 11:13
  • there's another nuget update in 2017 too which will fix other errors in the same method. the current template does not seem to have been updated so you must update the nuget packages for facebook OWIN yourself – Simon_Weaver Dec 03 '17 at 21:52

11 Answers11

26

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
  • 3,868
  • 1
  • 25
  • 18
  • 2
    This moves the problem on, but I now get a NullReferenceException when creating a username in the method: public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl) at the line: var info = await AuthenticationManager.GetExternalLoginInfoAsync() – Gracie Oct 25 '13 at 12:36
  • This is a great answer, but as noted, there are other problems with the OWIN provider. – Gracie Nov 04 '13 at 08:46
  • Same thing for me as for #Gracie -- "This moves the problem on, but I..." I will try to apply the same workaround to ExternalLoginConfirmation as was applied to ExternalLoginCallback. – subsci Nov 18 '13 at 07:44
  • 1
    I want to add that the Google and Microsoft external logins are working fine. Only Facebook is problematic. I am 97.5% certain my Facebook app is configured correctly, as I have had success with Facebook external login in VS2012 using the DefaultProvider (AspNetOPenAuth) – subsci Nov 18 '13 at 07:54
  • In ExternalLoginCallback, the *Identity* from Facebook has four claims. However for the *ClaimsType.Name* instead of **"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"** I see **"urn:facebook:name"**. So where in the pipeline is the fault? Seems like Facebook deviate from standard ClaimsType? – subsci Nov 18 '13 at 08:56
  • Yes I just saw the issue at Katana. In it you commented about the lack of a facebook username. I added username to facebook test account and in this case the bug does not manifest. – subsci Nov 20 '13 at 04:49
  • I modified ExternalLoginCallback: result.Identity.AddClaim(new Claim(ClaimTypes.Name, nameClaim.Value)); but this failed. Is this an issue with HttpContext not in sync with Thread.CurrentPrincipal? Where is the correct place to add claims: In ExternalLoginConfirmation? – subsci Nov 20 '13 at 04:59
  • 1
    The best place for you to add the missing claim is in FacebookAuthenticationProvider.OnAuthenticated event. context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Name)); – Hongye Sun Nov 21 '13 at 00:32
  • Sorry to jump in to comments lately. Although recent VS 2013 Update 3 works desired without changes for me I would also suggest for developers who works inside corporate firewall to use default proxy credential or in other words set proxy authentication. I have modified mine to have something like this – meetkichu Oct 09 '14 at 09:32
  • This problem still occurs years later with both Facebook and Google. – Justin Skiles Aug 19 '15 at 02:53
  • This absolutely fixed my issues in 2016. – Keith Adler Jan 01 '16 at 20:52
  • how to fix the issue in the ExternalLoginConfirmation method .. it still fails at this 'AuthenticationManager.GetExternalLoginInfoAsync();' – Kasparov92 Jun 17 '16 at 14:01
24

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;
}
Konstantin Dinev
  • 34,219
  • 14
  • 75
  • 100
Bryan Knox
  • 645
  • 7
  • 12
4

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.

Daniil T.
  • 1,145
  • 2
  • 13
  • 33
  • No matter if I was logging in with or without an external login provider, `ExternalLoginCallback` would throw a `NullPointerException`. Adding that oneliner to Startup.Auth.cs did the trick!! THANKS! – thilemann Nov 07 '14 at 00:10
3

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.

  • Update following NuGet Pacakges
    • 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);
    }
}
user3012760
  • 203
  • 2
  • 6
2

If your stack trace contains DotNetOpenAuth.AspNet then is the same bug as has apparently existed for two years in DotNetOpenAuth/DotNetOpenId.

NullReferenceException in DotNetOpenAuth

https://github.com/DotNetOpenAuth/DotNetOpenAuth/issues/317#issuecomment-29580565

The owner of those libraries indicate MS has abandoned them, although it looks from your defect like they are probably actually moved into MS code.

If so, does that mean OSS got buried into closed code?

Would love to see your stack trace.

Community
  • 1
  • 1
shannon
  • 8,664
  • 5
  • 44
  • 74
2

I faced the same problem, when I checked libraries, I was using Microsoft ASP.NET Identity Owin 1.0.0 I updated it to Microsoft ASP.NET Identity Owin 2.0.1 using command PM> Install-Package Microsoft.AspNet.Identity.Owin -Version 2.0.1 This fixed the issue.

Rahul Patel
  • 500
  • 3
  • 11
1

I started getting this in the latest VS 2013.3 template and realized the authentication wasn't playing nice with FormsAuthentication that I unnecessarily ported from one of my other projects. Here's what I did to fix it:

added <system.web><authentication mode="None" />...

added <system.webServer><modules><remove name="FormsAuthentication" /></modules>...

parliament
  • 21,544
  • 38
  • 148
  • 238
0

I had exactly the same problem by following the same tutorial. I solved it by doing the following two steps: 1> Visual Studio Menu->Tools->Library Package Manager->Manage NuGet Packages for Solution..., then install package: Microsoft.Owin.Host.SystemWeb 2> In the same window, click Update (left bar) and then update all the packages.

Hope this answer will help other people who have the same problem.

Leona
  • 377
  • 1
  • 4
  • 17
0

I was getting the same.

I noticed that my providers were configured before UseExternalSignInCookie was called, so I simply made sure UseExternalSignInCookie is called before my providers are configured and everything worked:

// This has to go first
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

// This must come later
app.UseGoogleAuthentication(
            "[ClientId]",
            "[ClientSecret]");
joshcomley
  • 28,099
  • 24
  • 107
  • 147
0

I thought I'd throw on some notes for Visual Studio 2015 templates / the latest boiler plate code for WebAPI 2. I was getting this problem with google authentication but figure its similar to facebook and the other social logins. I had the latest Owin and my other nuget packages were up-to-date. Turns out with the latest out-of-the-box web api 2 templates, I just needed to specifically request the "email" be included back from google. Without this line, the api/Account/Register call would error.

And of course make sure your app is registered with google and your site is allowed to call it. (Lots of good examples showing those steps.) https://console.developers.google.com/apis

Here's my adjustment in the App_Start\Startup.Auth.cs file:

var googleOptions = new GoogleOAuth2AuthenticationOptions()
{
    ClientId = "xxx",
    ClientSecret = "xxx"
};
googleOptions.Scope.Add("email"); //!! Add this !!
app.UseGoogleAuthentication(googleOptions);

Until I added the .Add("email"), line, the api/Account/RegisterExternal WebAPI 2 call (AccountController.cs) would return null from this section of RegisterExternal:

var info = await Authentication.GetExternalLoginInfoAsync();
if (info == null) //This would be true, and it would error.
{
      return InternalServerError();
}

Since this is one of the few articles that come up for this error, I figured I'd tag my notes on my solution for posterity. (especially the postman test process!)

So to make it all work in testing: 1) Call the api/Account/ExternalLogins URL like this:

http://localhost:59137/api/Account/ExternalLogins?returnUrl=%2F&generateState=true

You should get a response like this:

<ArrayOfExternalLoginViewModel xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/TCG_DL_API.Models">
<ExternalLoginViewModel>
<Name>Google</Name>
<State>1phegLF241xeSfd8gZAsCXiBAp3l5bMygg2VSeRXAHk1</State>
<Url>
/api/Account/ExternalLogin?provider=Google&response_type=token&client_id=self&redirect_uri=http%3A%2F%2Flocalhost%3A59137%2F&state=1phegLF241xeSfd8gZAsCXiBAp3l5bMygg2VSeRXAHk1
</Url>
</ExternalLoginViewModel>
</ArrayOfExternalLoginViewModel>

2) Then get the Url from the response, and call it. You should get the google login prompt/page. (Or I assume the facebook or twitter one, if that's what you set up.)

3) Login, and you'll get redirected back to your redirect page. It'll have a URL like something like this:

http://localhost:59137/#access_token=d5asC1arCUXaLEMgBS8PT_uwZcTJqC1UZbXblNZ3hMOh3TSKtEXYeKtyKBTv3WmLcaLGGomSvpRSFMfXPxpPvNRgjUVWAiqxtKfv3qWHNqfIMeu5j0eZrJDRAMTrYFgflSbEopAe909a31I4mQnJuvaiITHYPrLmqkm6J88HAVx8F981_q_tflu4A72k3KaB-m2wd0-p1jdQnNMlixM2Wfloh_niUTBIOYUPc1SkKWcZxuI6dzN2Z0PmWHDwzJI8nM8vOuzybJIsxLOyTY1VfzSQ5Qzcll3HhifLPkyZxvXDQ5LHqW1v0_AztsUWkEhW_AJzmw2IaOcTtHCmkmWm1K444okNtOsYfs6HFui0NeY&token_type=bearer&expires_in=1209600&state=3FSOd3_n_sEL4QtiELWPG5B2_H3wRjVb75uDjQS16gk1

grab the token (bold above) and use it as the bearer token.

Postman example of GET api/Account/UserInfo

4) Now since you aren't registered (but you do have a bearer token), you can call the POST api/Account/RegisterExternal

enter image description here

5) The response will be OK, and if you look in your AspnetUser tables, you'll see that you have a new AspnetUsers record and a new AspNetUserLogins record for google as the provider.

I hope this helps with anyone trying to get this stuff to work!

Greg
  • 763
  • 6
  • 8
0

I hadn't enabled the Google+ API and came back with access_denied when I looked at fiddler. Enabling the Google+ API sorted out the problem.

Liam
  • 5,033
  • 2
  • 30
  • 39