Get more information from Social providers used in the VS 2013 project templates

Get more information from Social providers used in the VS 2013 project templates

Rate This
  • Comments 27

When you create a New ASP.NET Project in VS 2013 and choose Individual Accounts, the template shows how you can login with Social providers such as Microsoft Account, Facebook, Google and Twitter. When you login with these Social Providers such as Facebook, you can request more information about the user such as the User’s picture, friends etc. and if the user allows your app to access this data then you can get this information and provide a rich experience in your site.

In the following post I am going to show you how you can request more data (or scopes) when a user logs in via Facebook provider. This post assumes that you have enabled Facebook login and are familiar with the basic walkthrough of Facebook Login.  You can visit http://www.asp.net/mvc/tutorials/mvc-5/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on to see a basic walkthrough on how to enable Facebook Login in the template.

[Update]: You can find the completed sample at https://github.com/rustd/FBLogin

Following are the steps to get more scopes from Facebook

Code Snippet
  1.  var x = new FacebookAuthenticationOptions();
  2.  x.Scope.Add("email");
  3.  x.Scope.Add("friends_about_me");
  4.  x.Scope.Add("friends_photos");
  5.  x.AppId = "YourAppId";
  6.  x.AppSecret = "YourAppSecret";
  7.  x.Provider = new FacebookAuthenticationProvider()
  8. {
  9.     OnAuthenticated = async context =>
  10.     {
  11.         //Get the access token from FB and store it in the database and
  12.         //use FacebookC# SDK to get more information about the user
  13.         context.Identity.AddClaim(
  14.         new System.Security.Claims.Claim("FacebookAccessToken",
  15.                                              context.AccessToken));
  16.     }
  17. };
  18.  x.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
  19.  app.UseFacebookAuthentication(x);
    • Line 2-5, we are specifying the scopes.
    • Line 9-15, we are hooking to the OnAuthenticated event for the Facebook OWIN authentication middleware. This method is called each time a user authenticates with Facebook.
      When the user is authenticated and has granted this app access to this data, all the data is stored in the FacebookContext in the Facebook authentication middleware.
    • Line 14, also stores the FacebookAccessToken which we get from Facebook and which we will use to get the Users’ friends information  

Store the FacebookAccessToken and use it in the app to get the list of friends and their pictures

    • Add a link in _LoginPartial.cshtml to display pictures of all friends

    • Code Snippet
      1. <li><a href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li>
      2. <li>
      3.          @Html.ActionLink("FacebookInfo", "FacebookInfo", "Account")
      4.     
      5. </li>
  • Get the FacebookAccessToken claim and store it in the UserClaims table using ASP.NET Identity
    • In the following code we get the Claim which was passed from Facebook Middleware to the app
    • StoreFacebookAuthToken gets the claims from the UserIdentity and persists the AccessToken in the database as a User Claim.
    • LinkLoginCallback action is called when the user is logged in and is associating another login provider.
Code Snippet
  1. //
  2.         // GET: /Account/LinkLoginCallback
  3.         publicasyncTask<ActionResult> LinkLoginCallback()
  4.         {
  5.             var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
  6.             if (loginInfo == null)
  7.             {
  8.                 return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
  9.             }
  10.             var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
  11.             if (result.Succeeded)
  12.             {
  13.                 var currentUser = await UserManager.FindByIdAsync(User.Identity.GetUserId());
  14.                 //Add the Facebook Claim
  15.                 await StoreFacebookAuthToken(currentUser);
  16.                 return RedirectToAction("Manage");
  17.             }
  18.             return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
  19.         }
  • ExternalLoginConfirmation action is called when you login with the Facebook provider for the first time.
    • In Line 26, once the User is created we add a new line to add  the FacebookAccessToken as a claim for the user.
Code Snippet
  1. [HttpPost]
  2.         [AllowAnonymous]
  3.         [ValidateAntiForgeryToken]
  4.         publicasyncTask<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
  5.         {
  6.             if (User.Identity.IsAuthenticated)
  7.             {
  8.                 return RedirectToAction("Manage");
  9.             }
  10.  
  11.             if (ModelState.IsValid)
  12.             {
  13.                 // Get the information about the user from the external login provider
  14.                 var info = await AuthenticationManager.GetExternalLoginInfoAsync();
  15.                 if (info == null)
  16.                 {
  17.                     return View("ExternalLoginFailure");
  18.                 }
  19.                 var user = newApplicationUser() { UserName = model.Email };
  20.                 var result = await UserManager.CreateAsync(user);
  21.                 if (result.Succeeded)
  22.                 {
  23.                     result = await UserManager.AddLoginAsync(user.Id, info.Login);
  24.                     if (result.Succeeded)
  25.                     {
  26.                         await StoreFacebookAuthToken(user);
  27.                         await SignInAsync(user, isPersistent: false);
  28.                         return RedirectToLocal(returnUrl);
  29.                     }
  30.                 }
  31.                 AddErrors(result);
  32.             }
  33.  
  34.             ViewBag.ReturnUrl = returnUrl;
  35.             return View(model);
  36.         }
  • ExternalLoginCallback action is called when you associate the User with an external login provider for the first time.
    • In line 17 we add a new line to add the FacebookAccessToken as a claim for the user.
Code Snippet
  1. //
  2.         // GET: /Account/ExternalLoginCallback
  3.         [AllowAnonymous]
  4.         publicasyncTask<ActionResult> ExternalLoginCallback(string returnUrl)
  5.         {
  6.             var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
  7.             if (loginInfo == null)
  8.             {
  9.                 return RedirectToAction("Login");
  10.             }
  11.  
  12.             // Sign in the user with this external login provider if the user already has a login
  13.             var user = await UserManager.FindAsync(loginInfo.Login);
  14.             if (user != null)
  15.             {
  16.                 //Save the FacebookToken in the database if not already there
  17.                 await StoreFacebookAuthToken(user);
  18.                 await SignInAsync(user, isPersistent: false);
  19.                 return RedirectToLocal(returnUrl);
  20.             }
  21.             else
  22.             {
  23.                 // If the user does not have an account, then prompt the user to create an account
  24.                 ViewBag.ReturnUrl = returnUrl;
  25.                 ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
  26.                 return View("ExternalLoginConfirmation", newExternalLoginConfirmationViewModel { Email = loginInfo.Email });
  27.             }
  28.         }

    • This stores the FacebookAccessToken as a User Claim in the ASP.NET Identity database

Code Snippet
  1. privateasyncTask StoreFacebookAuthToken(ApplicationUser user)
  2.         {
  3.             var claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
  4.             if (claimsIdentity != null)
  5.             {
  6.                 // Retrieve the existing claims for the user and add the FacebookAccessTokenClaim
  7.                 var currentClaims = await UserManager.GetClaimsAsync(user.Id);
  8.                 var facebookAccessToken = claimsIdentity.FindAll("FacebookAccessToken").First();
  9.                 if (currentClaims.Count() <=0 )
  10.                 {
  11.                     await UserManager.AddClaimAsync(user.Id, facebookAccessToken);
  12.                 }
Code Snippet
  1.    public class FacebookViewModel
  2.   {
  3.        [Required]
  4.        [Display(Name = "Friend's name")]
  5.        public string Name { get; set; }
  6.        public string ImageURL { get; set; }
  7.    }
  • Add the following Action in the Account Controller. This action gets the FacebookAccessToken and makes a call to Facebook using Facebook C# SDK to get the list of friends and their pictures.
Code Snippet
  1. //GET: Account/FacebookInfo
  2. [Authorize]
  3. publicasyncTask<ActionResult> FacebookInfo()
  4. {
  5.     var claimsforUser = await UserManager.GetClaimsAsync(User.Identity.GetUserId());
  6.     var access_token = claimsforUser.FirstOrDefault(x => x.Type == "FacebookAccessToken").Value;
  7.     var fb = newFacebookClient(access_token);
  8.     dynamic myInfo = fb.Get("/me/friends");
  9.     var friendsList = newList<FacebookViewModel>();
  10.     foreach (dynamic friend in myInfo.data)
  11.     {
  12.         friendsList.Add(newFacebookViewModel()
  13.            {
  14.                Name = friend.name,
  15.                ImageURL = @"https://graph.facebook.com/" + friend.id + "/picture?type=large"
  16.            });
  17.     }
  18.  
  19.     return View(friendsList);
  20. }
  • Add a new View FacebookInfo.cshtml under Views\Account and add the following markup
Code Snippet
  1. @model IList<FB.Models.FacebookViewModel>
  2.   @if (Model.Count > 0)
  3.   {
  4.       <h3>List of friends</h3>
  5.       <div class="row">
  6.               @foreach (var friend in Model)
  7.              {
  8.                 <div class="col-md-3">
  9.                   <a href="#" class="thumbnail">
  10.                    <img src=@friend.ImageURL alt=@friend.Name />
  11.                   </a>
  12.                 </div>
  13.                }
  14.       </div>
  15.   }
  • Run the project and log in using Facebook. You should be taken to the Facebook Site where when you successfully login and grant this app permissions to access this data, then you should be redirected back to the application.
  • When you click the FacebookInfo link, you should see your friends along with their profile pictures.

image

Conclusion

This was an easy way to extend the Social providers and get more information about the logged in user so you can provide a rich experience for the web site users. You can do this with the other Social Providers as well. If you have any questions, please visit the asp.net/forums or reach me via twitter (@rustd)

Leave a Comment
  • Please add 3 and 8 and type the answer here:
  • Post
  • This scrappy set of code extracts the claims you want from the OWIN Context, just call if from within ExternalLoginCallback, or other callbacks where you need the major claims exposed

    private async Task<ExtractedClaims> ExtractExternalClaims()

           {

               AuthenticateResult authResult =

                   await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie);

               if (authResult == null)

                   return null;

               if (!authResult.Identity.IsAuthenticated)

                   return null;

               ClaimsIdentity externalIdentity = authResult.Identity;

               Claim nameIdentifierClaim = externalIdentity.FindFirst(ClaimTypes.NameIdentifier);

               string issuer = nameIdentifierClaim.Issuer;

               string nameIdentifier = nameIdentifierClaim.Value;

               string name = externalIdentity.FindFirstValue(ClaimTypes.Name);

               string email = externalIdentity.FindFirstValue(ClaimTypes.Email);

               if (String.IsNullOrEmpty(issuer))

                   throw new NullReferenceException("The identity claims contain no issuer.");

               if (String.IsNullOrEmpty(nameIdentifier))

                   throw new NullReferenceException("The identity claims contain no provider key");

               var result = new ExtractedClaims

               {

                   ProviderKey = issuer,

                   IdentityKey = nameIdentifier,

                   Name = name,

                   Email = email,

               };

               return result;

           }

           private class ExtractedClaims

           {

               public String ProviderKey { get; set; }

               public String IdentityKey { get; set; }

               public String Name { get; set; }

               public String Email { get; set; }

           }

  • Re my last post, this works out of the box for Google. (Why can't they all be that easy). You have to muck about with Scope additions for Facebook and Live using the authentication options in the OWIN middleware.

    Adding this to Startup.Auth and calling it from ConfigureAuth works for me for Facebook.

    I have set up two DEV accounts one for live one for testing hence the #if DEBUG

         private static void UseFacebook(IAppBuilder app)

           {

               if (!Properties.Settings.Default.UseFacebookLogin) { return; }

               var facebookOptions = new FacebookAuthenticationOptions

               {

    #if DEBUG

                   AppId = "xxxxx",

                   AppSecret = "xxxxxxxxxxxx",

    #else

                   AppId = "yyyyyyyy",

                   AppSecret = "yyyyyyyyyyyyyyyyyyyyyyyy",

    #endif

                   Provider = new FacebookAuthenticationProvider

                   {

                       OnAuthenticated = context =>

                       {

                           var claim = new Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString,

                               "Facebook");

                           context.Identity.AddClaim(claim);

                           foreach (var x in context.User)

                           {

                               string claimType = string.Format("urn:facebook:{0}", x.Key);

                               string claimValue = x.Value.ToString();

                               if (!context.Identity.HasClaim(claimType, claimValue))

                               {

                                   context.Identity.AddClaim(new Claim(claimType, claimValue, XmlSchemaString, "Facebook"));

                               }

                           }

                           return Task.FromResult(0);

                       }

                   }

               };

               facebookOptions.Scope.Add("email");

               app.UseFacebookAuthentication(facebookOptions);

           }

  • OK Last snippet, I Promise

    It's all in the name :-)

    You need this to make that last bit work;

    string XmlSchemaString = "www.w3.org/.../XMLSchema

  • Sample Project would be usefull! :) Thanks in advance

  • This was exactly what I needed. Thanks Pranav!

  • Wow. Nice article. Very interesting and easy to work out. Thanks Pranav

  • Can we have a tutorial based on Web Forms?

  • To Mike and any others trying to get the user email address from a Twitter login - it's not possible. See twittercommunity.com/.../558 for a robust discussion of this.

  • I have followed this tutorial and everything seemed to be working fine, until I got the 190 OAuthException after a day or so while testing it out again.

    The error happens on this line:

    dynamic myInfo = fb.Get("/me/friends");

    As I understand this happens due to the expiration of the access token, so maybe you could suggest on how should the code be modified for a new access token to be retrieved?

    Thanks!

  • Suggestions for add full samples. (for Unit Test project VS 2013, and Windows Forms app too.)

    More about Facebook SDK C# for

    1) from me and other users:

    post to wall,

    post photo to album,

    download photos from album

    share post with another user (maybe send messages)

    share photo with another user (maybe send messages)

    share page with another user

    do like and unlike to a post

    do like and unlike to a page

    get friend list and info data

    2) from me

    delete post

    delete photo from album

    delete album

    create new album

  • Hi, I am using WebForms and I am trying to follow this tutorial by porting the sample code provided here to VB.Net.

    I have 2 problems when modifying the code in the Startup.Auth.vb:

    Dim MyFacebookOptions = New FacebookAuthenticationOptions()

           MyFacebookOptions.Scope.Add("email")

           MyFacebookOptions.Scope.Add("first_name")

           MyFacebookOptions.Scope.Add("last_name")

           MyFacebookOptions.Scope.Add("user_birthday")

           MyFacebookOptions.Scope.Add("user_tagged_places")

          MyFacebookOptions.AppId = "xxxxxxxxxxxxxx"

          MyFacebookOptions.AppSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

     MyFacebookOptions.Provider = New FacebookAuthenticationProvider() With {.OnAuthenticated = Function(context)

     context.Identity.AddClaim(New System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken))

                                                                                                      End Function

           }

           MyFacebookOptions.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie

           app.UseFacebookAuthentication(MyFacebookOptions)

    1) In Visual Studio the following alert is notified: The function ' < anonymous method > ' does not return a value on all code paths. You may be returned due to an exception with a reference value null at runtime when using the result.

    2) If I test my project, I receive the following error from Facebook: Invalid Scope: first_name

    Does anybody know where I am missing something and how to correct it?

  • Claudio, you're a bit confused, the scope is only the email that connects you to the public profile. You have to add a claim for the rest of the information. I'm using IF 2.0 at the time being so it's a bit different but this is how I do it:

    facebookOptions.Scope.Add("email");

               facebookOptions.Provider = new FacebookAuthenticationProvider()

               {

                   OnAuthenticated = async context =>

                   {

                       context.Identity.AddClaim(new Claim("FacebookAccessToken", context.AccessToken));

                       context.Identity.AddClaim(new Claim("FirstName", context.User.GetValue("first_name").ToString()));

                       context.Identity.AddClaim(new Claim("LastName", context.User.GetValue("last_name").ToString()));

                   }

               };

    The scope is correctly the email, but I add claims for the token (mapped to the access token), first_name field in Facebook mapped to FirstName and last_name. Then I can "claim back" those fields in the callback:

               var externalIdentity = HttpContext.GetOwinContext().Authentication.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

               var firstName = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type.Equals("FirstName"));

               var lastName = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type.Equals("LastName"));

    This is the way to do it.

Page 2 of 2 (27 items) 12