1

I realise this question has been asked and answered a lot in the last 10 years, but I need a more modern answer than most I could find.

I have an ASP.Net MVC project (C#), and have followed the MS guide to enabling Facebook and Google+ authentication. This was super simple, register the apps in the providers and uncomment three lines each in startup.auth.cs - super, super simple. It also "just works", bango I have new users created in the database that can login via these providers. Wunderbar.

Like so many applications though I need more than authentication, I also need a small amount of identity - firstname, surname, email, birthday, profile image where available.

Using a combination of Scope and Fields in the FacebookAuthenticationOptions I have managed to get the Facebook authorization screen to warn the user that the app wants their birthday, but try as I might I can't find anywhere that this information is returned in the OAuth process or any claims that might represent the half a dozen other fields I've asked for.

Many "solutions" online talked about overriding the "OnAuthenticated" delegate of the FacebookAuthenticationOptions.Provider (which is a FacebookAuthenticationProvider). I've pasted in some stolen code but it never hits the breakpoint in there.

After I get this working with Facebook, I had hoped to repeat with Google+, Twitter, and Microsoft account providers and I'm hoping it's a standardised approach where I can tell each provider what fields I want in their format, then get them all out using a standard getter somewhere. As I understand it that is actually the whole underlying point of OAuth - no?

So in my startup.auth.cs (fake Id and secret of course):

app.UseFacebookAuthentication(new FacebookAuthenticationOptions
      {
        AppId = "xxxxxxxxxxx",
        AppSecret = "xxxxxxxxxxxxxxxxxxxxx",
        Scope = { "user_birthday", "public_profile", "email" },
        Fields = { "user_birthday", "picture", "name", "email", "gender", "first_name", "last_name" },
        Provider = new FacebookAuthenticationProvider
        { 
          OnAuthenticated = async ctx =>
          {
            Console.WriteLine("auth"); //this breakpoint never hits
            ctx.Identity.AddClaim(new Claim("FacebookAccessToken", ctx.AccessToken));
            foreach (var claim in ctx.User)
            {
              var claimType = string.Format("urn:facebook:{0}", claim.Key);
              string claimValue = claim.Value.ToString();

              if (!ctx.Identity.HasClaim(claimType, claimValue))
              {
                ctx.Identity.AddClaim(new Claim(claimType, claimValue, "XmlSchemaString", "Facebook"));
              }
            }
          }

        }
      });
Jeff Whitty
  • 116
  • 7
  • This is possible, you might look at AWS Cognito service, it does all this and Azure's equivalent is AppCentre https://learn.microsoft.com/en-gb/appcenter/quickstarts/. I think you're going to have too surf the FB graph API to get the B'day: https://stackoverflow.com/questions/34927836/how-to-get-birthday-via-facebook-api - I didn't know you could get it from OAuth and it looks like you can, hopefully someone can clarify for us? – Jeremy Thompson Jul 02 '20 at 04:42
  • Thanks Jeremy, I had a read of AppCentre but I'm not connecting the dots on how that helps me include the functionality in an external application. – Jeff Whitty Jul 07 '20 at 05:22
  • 1
    More a suggestion of yes it can be done and a "why redesign the wheel if both AWS and Azure have services that do exactly the same". Back to your specific problem/requirement, check this out: https://stackoverflow.com/q/25966530/495455, see the second answer... – Jeremy Thompson Jul 07 '20 at 05:27
  • I appreciate the intent Jeremy, I think my point was that I couldn't see how AppCentre was a service that did that. I can see that it does provide the same functionality within its services, but I couldn't see how I could tap into it as a service. – Jeff Whitty Jul 07 '20 at 05:30
  • Yes, so I use AWS Cognito service for a mobile app. I'm studying Azure and the equivilent is https://learn.microsoft.com/en-us/azure/architecture/aws-professional/services AppCentre. I don't think its really the same on closer inspection, see how Cognito is more aligned to what you're doing with Name, Phone, B'day etc with all providers: https://i.stack.imgur.com/pKr8S.png To tap into it AppCentre or Cognito from your app you use them via REST web service calls with JSON. – Jeremy Thompson Jul 07 '20 at 05:34

1 Answers1

0

Thanks to @Jeremy Thompson in the comments that pointed me to what I originally thought was just another copy of the same thing I stole in the first place, but actually triggered me to solve this myself in a way.

The solution Jeremy linked to "worked", it looked much like my original code except the OnAuthenticated method was firing. Unfortunately it didn't actually have the birthday in it, so I added it, and then it stopped working. Aha...

So glazing over the how I got there with this, the problem seems to be that a silent exception occurs which prevents this OnAuthenticated from firing which in my case was because I had a field name incorrect. Here is the working code which returns my name, email, birthday, current town, and a profile picture (the essentials you need to create a profile in your own system!)

In the OWIN startup class of course:

app.UseFacebookAuthentication(new FacebookAuthenticationOptions
      {
        AppId = "...", //Your App Id of course
        AppSecret = "...", //Your App Secret of course
        Scope = { "user_birthday", "public_profile", "email", "user_gender", "user_location" },
        Fields = { "birthday", "picture", "name", "email", "gender", "first_name", "last_name", "location" }, //notice the fields are not prefixed with "user_". This was 
        Provider = new FacebookAuthenticationProvider
        {
          OnAuthenticated = async ctx =>
          {
            ctx.Identity.AddClaim(new Claim("FacebookAccessToken", ctx.AccessToken));
            foreach (var claim in ctx.User)
            {
              var claimType = string.Format("urn:facebook:{0}", claim.Key);
              string claimValue = claim.Value.ToString();

              if (!ctx.Identity.HasClaim(claimType, claimValue))
              {
                ctx.Identity.AddClaim(new Claim(claimType, claimValue, "XmlSchemaString", "Facebook"));
              }
            }
          }

        }
      });

And now in my AccountController's ExternalLoginCallback function I can access a heap of properties by looking at the loginInfo.Claims property (loginInfo as below).

      var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
Jeff Whitty
  • 116
  • 7