MVC 5 Owin Facebook Auth results in Null Reference Exception
Asked Answered
D

11

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.

Degression answered 24/10, 2013 at 11:19 Comment(2)
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.Lexy
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 yourselfCarrick
T
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.

Tao answered 24/10, 2013 at 17:57 Comment(12)
This moves the problem on, but I now get a NullReferenceException when creating a username in the method: public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl) at the line: var info = await AuthenticationManager.GetExternalLoginInfoAsync()Degression
This is a great answer, but as noted, there are other problems with the OWIN provider.Degression
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.Averse
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)Averse
In ExternalLoginCallback, the Identity from Facebook has four claims. However for the ClaimsType.Name instead of "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?Averse
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.Averse
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?Averse
The best place for you to add the missing claim is in FacebookAuthenticationProvider.OnAuthenticated event. context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Name));Tao
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 <system.net> <defaultProxy useDefaultCredentials="true"> <proxy usesystemdefault="true"/> </defaultProxy> </system.net>modified mine to have something like thisInexcusable
This problem still occurs years later with both Facebook and Google.Frampton
This absolutely fixed my issues in 2016.Neighbors
how to fix the issue in the ExternalLoginConfirmation method .. it still fails at this 'AuthenticationManager.GetExternalLoginInfoAsync();'Keto
S
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;
}
Speedball answered 30/12, 2013 at 20:59 Comment(2)
This must still be a bug in the latest release because I was having this issue until this method fixed it for me.Frampton
added this to the loginInfo object too: ExternalIdentity = result.IdentityPlay
V
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.

Vanya answered 20/10, 2014 at 17:3 Comment(1)
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!Nutter
A
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);
    }
}
Actuary answered 31/3, 2017 at 12:2 Comment(0)
B
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.

Buprestid answered 2/12, 2013 at 5:55 Comment(0)
L
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.

Lesialesion answered 6/7, 2014 at 10:6 Comment(0)
D
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>...

Dovecote answered 30/8, 2014 at 0:41 Comment(0)
U
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.

Unblushing answered 10/1, 2015 at 14:58 Comment(0)
D
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]");
Davies answered 7/12, 2015 at 4:7 Comment(0)
L
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!

Liveried answered 2/3, 2016 at 8:42 Comment(2)
At Step 4, var info = await Authentication.GetExternalLoginInfoAsync(); return null. What should I do?Defile
it doesnt make any change.Lily
B
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.

Behold answered 1/6, 2016 at 18:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.