Usman
Usman

Reputation: 2577

how to get first_name and last_name from facebook OAuth in MVC 5.0 C#?

I need to get first_name and last_name from Facebook v2_5 OAuth. in C#, MVC 5.I am using Microsoft.Owin.Security.Facebook 3.0.1.0.

`var record = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:name");`

I am getting name by using this, but I need first_name and last_name separately. Any help will be appreciated.Thanks in advance.

Upvotes: 2

Views: 2890

Answers (3)

andyrandy
andyrandy

Reputation: 73984

It´s called "Declarative Fields", see changelog for v2.4: https://developers.facebook.com/docs/graph-api/changelog/archive#v2_4_new_features

This would be the API call to get first_name and last_name:

/me?fields=name,first_name,last_name

Upvotes: 2

Gebb
Gebb

Reputation: 6546

A slight variation on Chris's answer. The trick is to add claims with well-known types taking values from the first_name and last_name fields sent by Facebook. For some reason the Microsoft library doesn't do it.

var facebookOptions = new FacebookAuthenticationOptions
{
    AppId = "*",
    AppSecret = "*",
};
const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
facebookOptions.Provider = new FacebookAuthenticationProvider
{
    OnAuthenticated = context =>
    {
        void addClaim(
            FacebookAuthenticatedContext ctx,
            string faceBookClaim,
            string aspNetClaim)
        {
            if (context.User.TryGetValue(faceBookClaim, out JToken t))
            {
                ctx.Identity.AddClaim(
                    new Claim(
                        aspNetClaim,
                        t.ToString(),
                        XmlSchemaString,
                        facebookOptions.AuthenticationType));
            }
        }

        addClaim(context, "first_name", ClaimTypes.GivenName);
        addClaim(context, "last_name", ClaimTypes.Surname);
        return Task.CompletedTask;
    }
};

facebookOptions.Scope.Add("public_profile");
facebookOptions.Scope.Add("email");

facebookOptions.Fields.Add("email");
facebookOptions.Fields.Add("name");
facebookOptions.Fields.Add("first_name");
facebookOptions.Fields.Add("last_name");
app.UseFacebookAuthentication(facebookOptions);

Upvotes: 0

Chris Moschini
Chris Moschini

Reputation: 37947

Just using an endpoint wasn't enough, and turns out to be unnecessary with the current OWIN Facebook lib, 3.1.0 - you can now specify the Fields in the options you pass.

I had to use a custom Authentication Provider that copes with a problem in the OWIN Facebook lib, where Facebook returns the fields, but the OWIN lib fails to parse and gather them for you. I suspect this is because the lib was designed such that you make a Claim, then retrieve the response to that Claim; but if you just name a Field, for some reason it doesn't parse that out at all.

In Startup.Auth:

var facebook = new FacebookAuthenticationOptions
{
    Provider = Brass9.OwinVisitor.Auth.Facebook.
        FirstLastNameFacebookAuthenticationProvider.SplitFirstLastName(),
#if DEBUG
    AppId = //
    AppSecret = //
#else
    AppId = //
    AppSecret = //
#endif
};
//id,name,email,first_name,last_name
var fbScope = facebook.Scope;
fbScope.Add("email");
//fbScope.Add("first_name");    // Uncommenting this line will break auth
facebook.Fields.Add("id");
facebook.Fields.Add("email");
facebook.Fields.Add("name");
facebook.Fields.Add("first_name");
facebook.Fields.Add("last_name");
app.UseFacebookAuthentication(facebook);

So, so far I've used the new OWIN Fields options to specify id, email, name, first_name, last_name - rather than passing a custom Endpoint in. I've also specified a custom AuthenticationProvider. Here it is:

namespace Brass9.OwinVisitor.Auth.Facebook
{
    public static class FirstLastNameFacebookAuthenticationProvider
    {
        public static FacebookAuthenticationProvider SplitFirstLastName()
        {
            return new FacebookAuthenticationProvider
            {
OnAuthenticated = async 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"));
    }
}
            };
        }
    }
}

This makes up for the OWIN lib's failure to parse out these values, depositing them as claims in the loginInfo user object.

Finally, as a convenience, I use one last class to get at these values in a canonical way across providers, since different providers offer first and last name using different schemas:

namespace Brass9.OwinVisitor.Auth
{
    public class ReducedClaims
    {
        protected Dictionary<string, string> claims;

        public ReducedClaims(IEnumerable<System.Security.Claims.Claim> claims)
        {
            var _claims = claims.ToArray();
            this.claims = new Dictionary<string, string>(_claims.Length);

            foreach(var claim in _claims)
            {
                this.claims.Add(claim.Type, claim.Value);
            }
        }

        public ReducedClaims(ExternalLoginInfo loginInfo)
            : this(loginInfo.ExternalIdentity.Claims)
        {}

        //http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
        //http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname



        protected string chainValue(params string[] keys)
        {
            string val = null;
            foreach(var key in keys)
            {
                if (claims.TryGetValue(key, out val))
                    return val;
            }

            return val;
        }

        // TODO: Instead detect which service it is then use the proper string instead of just milling through guesses?
        public string FirstName { get { return chainValue(
            "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",  // Google
            "urn:facebook:first_name"   // Facebook
        ); } }

        public string LastName { get { return chainValue(
            "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",    // Google
            "urn:facebook:last_name"    // Facebook
        ); } }
    }
}

Which you can call like:

var reducedClaims = new ReducedClaims(loginInfo.ExternalIdentity.Claims);
var firstName = reducedClaims.FirstName;
var lastName = reducedClaims.LastName;

Upvotes: 3

Related Questions