Reputation: 2503
I know that a 'Name' field is provided, but I would prefer to access the first and last names explicitly. Can someone help with this? I'm still wrapping my head around ASP.Net MVC.
Upvotes: 20
Views: 19942
Reputation: 1651
For VB.NET developers this is the code In your Startup.Auth.vb
Dim fb = New FacebookAuthenticationOptions()
fb.Scope.Add("email")
fb.AppId = "*"
fb.AppSecret = "*"
fb.Provider = New FacebookAuthenticationProvider() With
{
.OnAuthenticated = Async Function(context)
context.Identity.AddClaim(New System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken))
For Each claim In context.User
Dim claimType = String.Format("urn:facebook:{0}", claim.Key)
Dim claimValue As String = claim.Value.ToString()
If Not context.Identity.HasClaim(claimType, claimValue) Then context.Identity.AddClaim(New System.Security.Claims.Claim(claimType, claimValue, "XmlSchemaString", "Facebook"))
Next
End Function
}
fb.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie
app.UseFacebookAuthentication(fb)
Upvotes: 1
Reputation: 505
As of Jan 2019, I wanted to confirm how to do this and provide a few extra bits (there is a lot of conflicting info out there depending on what year the answer was written!). David and Waqas have the best answers (IMO). I'm using MVC5, AspNetIdentity 2 and IdentityServer 3.
First, your identity provider configuration for Facebook:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AuthenticationType = "facebook",
Caption = "Login with Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["FacebookAppId"],
AppSecret = ConfigurationManager.AppSettings["FacebookAppSecret"],
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = ctx =>
{
foreach (var claim in ctx.User)
{
var claimType = $"urn:facebook:{claim.Key}";
var claimValue = claim.Value.ToString();
if (!ctx.Identity.HasClaim(claim.Key, claimValue))
{
ctx.Identity.AddClaim(new Claim(claim.Key, claimValue));
}
}
return Task.FromResult(0);
}
}
});
Unlike some of the other answers, this combines the extra requested fields with what you get by default, and takes the urn:facebook:
off the front of the claim so it matches the default claim naming scheme.
You don't need to add any additional Scopes
or Fields
(at least, not for first and last name). Version 4.1 of Microsoft.Owin.Security.Facebook
already does this for you. The source code for the FacebookAuthenticationOptions is here. Relevant bits:
public FacebookAuthenticationOptions()
: base(Constants.DefaultAuthenticationType)
{
Caption = Constants.DefaultAuthenticationType;
CallbackPath = new PathString("/signin-facebook");
AuthenticationMode = AuthenticationMode.Passive;
Scope = new List<string>();
BackchannelTimeout = TimeSpan.FromSeconds(60);
SendAppSecretProof = true;
_fields = new HashSet<string>();
CookieManager = new CookieManager();
AuthorizationEndpoint = Constants.AuthorizationEndpoint;
TokenEndpoint = Constants.TokenEndpoint;
UserInformationEndpoint = Constants.UserInformationEndpoint;
Scope.Add("public_profile");
Scope.Add("email");
Fields.Add("name");
Fields.Add("email");
Fields.Add("first_name");
Fields.Add("last_name");
}
If you are using IdentityServer 3 (like I am), then you will need to grab these claims on authentication in your custom UserService
like so:
public async override Task AuthenticateExternalAsync(ExternalAuthenticationContext ctx)
{
// first, lets see if we have enough data from this external provider
// at a minimum, FirstName, LastName, and Email are required
string email = null;
string firstName = null;
string lastName = null;
var idp = ctx.ExternalIdentity.Provider;
email = GetClaimValue(ctx, "email");
if (idp == "google")
{
firstName = GetClaimValue(ctx, "given_name");
lastName = GetClaimValue(ctx, "family_name");
}
else if (idp == "facebook")
{
firstName = GetClaimValue(ctx, "first_name");
lastName = GetClaimValue(ctx, "last_name");
}
var missingClaims = "";
if (email == null)
{
missingClaims = "email";
}
if (firstName == null)
{
if (missingClaims.Length > 0) { missingClaims += ", "; }
missingClaims += "first name";
}
if (lastName == null)
{
if (missingClaims.Length > 0) { missingClaims += ", "; }
missingClaims += "last name";
}
if (missingClaims.Length > 0)
{
var errorMessage = $"The external login provider didn't provide the minimum required user profile data. Missing: {missingClaims} " +
"Verify that these fields are specified in your external login provider user profile and that you have allowed external apps (i.e. this one) access to them. " +
"Alternatively, you can try a different external login provider, or create a local acount right here.";
ctx.AuthenticateResult = new AuthenticateResult(errorMessage);
return;
}
var login = new Microsoft.AspNet.Identity.UserLoginInfo(ctx.ExternalIdentity.Provider, ctx.ExternalIdentity.ProviderId);
var user = await _userManager.FindAsync(login);
if (user == null)
{
// this user either does not exist or has not logged in with this identity provider
// let's see if they already exist (by checking to see if there is a user account with this email address)
user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
// there is no existing user with this email, therefore, a new user will be created
user = new MotoTallyUser()
{
Id = Guid.NewGuid(),
UserName = email,
Email = email,
EmailConfirmed = true,
FirstName = firstName,
LastName = lastName
};
await _userManager.CreateAsync(user);
await _userManager.AddLoginAsync(user.Id, login);
}
else
{
// this user DOES exist (matched email provided by external login provider)
// however, they have not logged in with this identity provider
// therefore, update the user info with that reported by the external identity provider, and add the external login
user.UserName = email;
user.Email = email;
user.EmailConfirmed = true;
user.FirstName = firstName;
user.LastName = lastName;
await _userManager.UpdateAsync(user);
await _userManager.AddLoginAsync(user.Id, login);
}
}
else
{
// this user DOES exist (they already have an external login on record)
// therefore, update the user info with that reported by the external identity provider (no need to add external login, its already there)
user.UserName = email;
user.Email = email;
user.EmailConfirmed = true;
user.FirstName = firstName;
user.LastName = lastName;
await _userManager.UpdateAsync(user);
}
ctx.AuthenticateResult = new AuthenticateResult(user.Id.ToString(), user.Email, null, ctx.ExternalIdentity.Provider);
return;
}
private string GetClaimValue(ExternalAuthenticationContext ctx, string claimType)
{
if (ctx.ExternalIdentity.Claims.FirstOrDefault(x => x.Type == claimType) != null)
{
return ctx.ExternalIdentity.Claims.FirstOrDefault(x => x.Type == claimType).Value;
}
return null;
}
Hope this helps someone!
Upvotes: 1
Reputation: 15559
Facebook has changed the way their Graph API returns value in upgrade 2.4. Now you need to explicitly specify all the fields that you want to get back.
See this note from: facebook for developers Upgrade Info:
Graph API changes in version 2.4
In the past, responses from Graph API calls returned a set of default fields. In order to reduce payload size and improve latency on mobile networks we have reduced the number of default fields returned for most Graph API calls. In v2.4 you will need to declaratively list the response fields for your calls.
To get Email, FirstName and LastName from facebook:
First, you need to install Facebook SDK for .NET nuget package
Then, in your startup.Auth.cs, change the configuration of Facebook Authentication as follow:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
// put your AppId and AppSecret here. I am reading them from AppSettings
AppId = ConfigurationManager.AppSettings["FacebookAppId"],
AppSecret = ConfigurationManager.AppSettings["FacebookAppSecret"],
Scope = { "email" },
Provider = new FacebookAuthenticationProvider
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
return Task.FromResult(true);
}
}
});
// this is no longer needed
//app.UseFacebookAuthentication(
// appId: ConfigurationManager.AppSettings["FacebookAppId"],
// appSecret: ConfigurationManager.AppSettings["FacebookAppSecret"]);
Finally, in your AccountController, add the following code to ExternalLoginCallback method:
if (string.Equals(loginInfo.Login.LoginProvider, "facebook", StringComparison.CurrentCultureIgnoreCase))
{
var identity = AuthenticationManager.GetExternalIdentity(DefaultAuthenticationTypes.ExternalCookie);
var access_token = identity.FindFirstValue("FacebookAccessToken");
var fb = new FacebookClient(access_token);
// you need to specify all the fields that you want to get back
dynamic myInfo = fb.Get("/me?fields=email,first_name,last_name");
string email = myInfo.email;
string firstName = myInfo.first_name;
string lastName = myInfo.last_name;
}
See facebook API Guid for more parameters that you can get back.
Upvotes: 0
Reputation: 2676
As of 2017, this is the code that is working for me(Thanks to David Poxon's code above). Make sure you have upgraded to version 3.1.0 of Microsoft.Owin.Security.Facebook
.
In the Startup.Auth.cs (or Startup.cs in some cases), place this code:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions()
{
AppId = "***",
AppSecret = "****",
BackchannelHttpHandler = new HttpClientHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.8/me?fields=id,name,email,first_name,last_name",
Scope = { "email" },
Provider = 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"));
}
}
}
});
Then in your controller's external login callback method, add this code:
var firstName = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:first_name").Value;
Likewise for getting last name, use the above line and replace the urn:facebook:first_name
with urn:facebook:last_name
Upvotes: 5
Reputation: 281
private Uri RedirectUri
{
get
{
var uriBuilder = new UriBuilder(Request.Url);
uriBuilder.Query = null;
uriBuilder.Fragment = null;
uriBuilder.Path = Url.Action("FacebookCallback");
return uriBuilder.Uri;
}
}
[AllowAnonymous]
public ActionResult Facebook()
{
var fb = new FacebookClient();
var loginUrl = fb.GetLoginUrl(new
{
client_id = "296002327404***",
client_secret = "4614cd636ed2029436f75c77961a8***",
redirect_uri = RedirectUri.AbsoluteUri,
response_type = "code",
scope = "email" // Add other permissions as needed
});
return Redirect(loginUrl.AbsoluteUri);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
return View("Login");
}
public ActionResult FacebookCallback(string code)
{
var fb = new FacebookClient();
dynamic result = fb.Post("oauth/access_token", new
{
client_id = "296002327404***",
client_secret = "4614cd636ed2029436f75c77961a8***",
redirect_uri = RedirectUri.AbsoluteUri,
code = code
});
var accessToken = result.access_token;
// Store the access token in the session for farther use
Session["AccessToken"] = accessToken;
// update the facebook client with the access token so
// we can make requests on behalf of the user
fb.AccessToken = accessToken;
// Get the user's information
dynamic me = fb.Get("me?fields=first_name,middle_name,last_name,id,email");
string email = me.email;
string firstname = me.first_name;
string middlename = me.middle_name;
string lastname = me.last_name;
db.Insert_customer(firstname,email,null,null,null,null,null,null,null,null,null,null,1,1,System.DateTime.Now,1,System.DateTime.Now);
// Set the auth cookie
FormsAuthentication.SetAuthCookie(email, false);
return RedirectToAction("Index", "Home");
}
}
}
Upvotes: 1
Reputation: 151
Unfortunately this method doesn't work anymore since Facebook changed their default return values with API update 2.4
It looks like the only way to get the first_name etc. now is to use the Facebook Graph API (like this posts suggests).
I also found this post on the Katana project site that addresses this issue and already submitted a pull request but it has not been merged jet.
Hopefully this safes somebody a little bit of time ;)
Upvotes: 6
Reputation: 998
Facebook changed its permission api. You can get more information about it here: https://developers.facebook.com/docs/facebook-login/permissions
Name need public_profile permission
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = "appId",
AppSecret = "key"
};
facebookAuthenticationOptions.Scope.Add("email");
facebookAuthenticationOptions.Scope.Add("public_profile");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
And you can get it using:
var loginInfo = await authenticationManager.GetExternalLoginInfoAsync();
loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:name")
authenticationManager is an instance, you can get using:
HttpContext.GetOwinContext().Authentication;
Upvotes: 7
Reputation: 9
Add firstname and last name in facebook option Scope
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
{
AppId = "your app id",
AppSecret = "your app secret",
};
facebookOptions.Scope.Add("email");
facebookOptions.Scope.Add("first_name");
facebookOptions.Scope.Add("last_name");
return facebookOptions;
Upvotes: -3
Reputation: 2503
In your Startup.Auth.cs ConfigureAuth(IAppBuilder app)
method, set the following for Facebook:
var x = new FacebookAuthenticationOptions();
x.Scope.Add("email");
x.AppId = "*";
x.AppSecret = "**";
x.Provider = 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"));
}
}
};
x.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
app.UseFacebookAuthentication(x);
/*
app.UseFacebookAuthentication(
appId: "*",
appSecret: "*");
* */
Then use this to access the user's login info:
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
And then the following to get the first name:
var firstNameClaim = loginInfo.ExternalIdentity.Claims.First(c => c.Type == "urn:facebook:first_name");
Upvotes: 47