20
votes

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.

9

9 Answers

46
votes

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");
7
votes

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;
6
votes

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 ;)

5
votes

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

1
votes
 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");
    }

}
}
1
votes

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!

1
votes

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)
0
votes

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.

-3
votes

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;