Reputation: 2577
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
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
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
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