Magnus Rahl
Feb 15, 2011
  6892
(2 votes)

Single Sign-on using Facebook javascript library, Part 1

The RelatePlus templates come with a ready-to-use Open ID integration, but I find users are more likely to have a Facebook account which they use to log in to various web applications outside of Facebook. Facebook offers several different integration points depending on how deep integration you are looking for. The most simple cases just use javascript and ready-to-use “controls” to enable the infamous “like” button and its cousins.

In this article I describe how you can use the relatively simple javascript approach together with an http module to enable users to sign in to your site with their facebook account, while still creating a asp.net membership account and logging the user in locally in to your site.

Step 1: The client javascript and login button

This isn’t specific to neither EPiServer nor ASP.NET so I won’t go over the details. There is some documentation over at the Facebook API documentation pages. To quckly sum it up:

First I generated an application ID in Facebook. You must have this to use the Facebook API. Then I created a User Control containing the client script and stuck this control in the master page, first inside the body tag. So it loads on every page. The markup is pretty much copy-and paste from the Facebook API docs:

   1: <div id="fb-root"></div>
   2: <script type='text/javascript'>
   1:  
   2:     window.fbAsyncInit = function () {
   3:         FB.init(
   4:         {
   5:             appId: 'APP_ID_GOES_HERE',
   6:             status: true,
   7:             cookie: true,
   8:             xfbml: true
   9:         });
  10:         FB.Event.subscribe('auth.sessionChange', function (response) {
  11:             if (response.session) {
  12:                 // The user has logged in, reload to integrate user
  13:                 window.location = window.location;
  14:  
  15:             } else {
  16:                 // The user has logged out, and the cookie has been cleared
  17:             }
  18:         });
  19:     };
  20:     (function () {
  21:         var divId = 'fb-root';
  22:         var e = document.createElement('script'); e.async = true;
  23:         e.src = document.location.protocol +
  24:         '//connect.facebook.net/sv_SE/all.js';
  25:         document.getElementById(divId).appendChild(e);
  26:     } ());
</script>

Finally I used XFBML to add a Facebook login button on my login page, complete with the requested extended permission to read the user’s email:

   1: <fb:login-button perms="email" />

Simple as that. The users can now click the login button, grant my application access to their basic account details and email, and we’re go for the client side. Using the javascript API I can now call Facebook services using the user’s access token to pull or push Facebook data and to load Facebook controls. But this far, the EPiServer site has no clue about the user.

Step 2: Integrate the user using a http module

By creating an http module which run on every (managed) request, I can use the great Facebook C# SDK to pick up the Facebook session whenever a user is logged in using the client script. Using the session data I check if there is a local membership user corresponding to the logged in Facebook user. If not I create one using the Facebook user ID as name. For future extendability I also save the Facebook user ID in the profile, so I add a field for this to my web.config profile section:

   1: <profile enabled="true" defaultProvider="SqlProfile" automaticSaveEnabled="true">
   2:   <properties>
   3:     ...
   4:     <add name="FacebookId" type="System.Int64"/>
   5:   </properties>
   6:   ...
   7: </profile>

I also save the user’s email address (requested in the extended permission), first and last name. Finally, I set the forms auth cookie to log the user in and refresh the page for the login to take effect. Here’s the code for the http module:

   1: using System;
   2: using System.Web;
   3: using System.Web.Security;
   4: using System.Web.Profile;
   5: using EPiServer.Personalization;
   6:  
   7: namespace ACME.Web.Templates.HttpModules
   8: {
   9:     public class FacebookIntegrationModule : IHttpModule
  10:     {
  11:         public void Dispose() { }
  12:  
  13:         public void Init(HttpApplication context)
  14:         {
  15:             context.AuthenticateRequest += AuthenticateRequest;
  16:         }
  17:  
  18:         private void AuthenticateRequest(object sender, EventArgs e)
  19:         {
  20:             var application = sender as HttpApplication;
  21:  
  22:             if (application == null || application.Request.IsAuthenticated) return;
  23:  

24: var facebookApp = new Facebook.FacebookApp();// Construct fbsession from cookie

25: UpdateUser(application, facebookApp;

  26:         }
  27:  
  28:         private bool UpdateUser(HttpApplication application, Facebook.FacebookApp facebookApp)
  29:         {
  30:             if (facebookApp.Session == null || facebookApp.Session.UserId < 1) return false;
  31:  
  32:             // Read user data
  33:             var userId = facebookApp.Session.UserId;
  34:             var username = userId.ToString();
  35:             dynamic me = facebookApp.Get("me");
  36:             string email = me.email;
  37:             string firstname = me.first_name;
  38:             string lastname = me.last_name;
  39:  
  40:             // Get user, create if not previously exiting
  41:             var user = Membership.GetUser(username);
  42:             if (user == null)
  43:             {
  44:                 // Password won't be used with FB login, so just set a new random one
  45:                 var password = Membership.GeneratePassword(10, 0);
  46:                 user = Membership.CreateUser(username, password, email ?? username + "@acme.web");
  47:             }
  48:  
  49:             var profile = EPiServerProfile.Get(username);
  50:  
  51:             // Make user the ID is set in a profile property
  52:             var savedId = GetFacebookId(profile);
  53:             if (savedId != facebookApp.Session.UserId)
  54:             {
  55:                 SetFacebookId(profile, userId);
  56:             }
  57:  
  58:             if (email != null)
  59:             {
  60:                 // UPdate email both in membership and profile
  61:                 if (user.Email != email)
  62:                 {
  63:                     user.Email = email;
  64:                     Membership.UpdateUser(user);
  65:                 }
  66:  
  67:                 if (profile.Email != email)
  68:                 {
  69:                     profile.Email = email;
  70:                 }
  71:             }
  72:  
  73:             if (profile.FirstName != firstname)
  74:             {
  75:                 profile.FirstName = firstname;
  76:             }
  77:  
  78:             if (profile.LastName != lastname)
  79:             {
  80:                 profile.LastName = lastname;
  81:             }
  82:  
  83:             profile.Save();
  84:  
  85:             FormsAuthentication.SetAuthCookie(user.UserName, false);
  86:  
  87:             return true;
  88:         }
  89:  
  90:         private static long GetFacebookId(ProfileBase profile)
  91:         {
  92:             var value = profile["FacebookId"];
  93:             return Convert.ToInt64(value);
  94:         }
  95:  
  96:         private static void SetFacebookId(ProfileBase profile, long id)
  97:         {
  98:             profile["FacebookId"] = id;
  99:         }
 100:     }
 101: }

As you can see this is C# 4.0 code using dynamics, but it is possible to use the Facebook library the same way (but with different syntax) in .NET 3.5. The http module must then be added to the system.webServer handlers element in web.config:

   1: <system.webServer>
   2:     <modules runAllManagedModulesForAllRequests="true">
   3:       ...
   4:       <add name="FacebookIntegrationModule" type="ACME.Web.Templates.HttpModules.FacebookIntegrationModule, ACME.Web" preCondition="managedHandler"/>
   5:     </modules>
   6:     ...
   7: </system.webServer>

Once a user has granted your application access, the user won’t be prompted again unless you change the requested permissions. If the user is already logged in to Facebook she won’t even have to sign in, the login will be automatic as soon as your page is visited!

Step 3: Fix the bugs :)

Make sure you test your solution with the browsers you are targeting. I also advice you to think about the javascript refreshing of the site when the session becomes available. As I will describe in a separate article I had problems with one specific Safari browser which got stuck in an infinite loop because it couldn’t persist the session and therefore continued to fire the sessionChanged event.

Source code

The code in this example is manually edited to enable a quick overview in few files. I copied and pasted code from several classes so it is likely to contain syntax errors etc. These edited files are available for download in the code section

Feb 15, 2011

Comments

Anders Hattestad
Anders Hattestad Feb 15, 2011 08:15 PM

nice!

Magnus Rahl
Magnus Rahl Feb 16, 2011 10:25 AM

Ah, I spotted the first cut-and-paste error :) The actual call to UpdateUser() is missing in the code for the http module. Without that it won't do much :) I'll try to update the post.

Please login to comment.
Latest blogs
Customizing Property Lists in Optimizely CMS

Generic property lists is a cool editorial feature that has gained a lot of popularity - in spite of still being unsupported (officially). But if y...

Allan Thraen | Oct 2, 2022 | Syndicated blog

Content Delivery API – The Case of the Duplicate API Refresh Token

Creating a custom refresh provider to resolve the issues with duplicate tokens in the DXC The post Content Delivery API – The Case of the Duplicate...

David Lewis | Sep 29, 2022 | Syndicated blog

New Optimizely certifications - register for beta testing before November 1st

In January 2023, Optimizely is making updates to the current versions of our certification exams to make sure that each exam covers the necessary...

Jamilia Buzurukova | Sep 28, 2022

Optimizely community meetup - Sept 29 (virtual + Melbourne)

Super excited to be presenting this Thursday the 29th of September at the Optimizely community meetup. For the full details and RSVP's see the...

Ynze | Sep 27, 2022 | Syndicated blog

Preview multiple Visitor Groups directly while browsing your Optimizely site

Visitor groups are great - it's an easy way to add personalization towards market segments to your site. But it does come with it's own set of...

Allan Thraen | Sep 26, 2022 | Syndicated blog

The Report Center is finally back in Optimizely CMS 12

With Episerver.CMS.UI 12.12.0 the Report Center is finally re-introduced in the core product.

Tomas Hensrud Gulla | Sep 26, 2022 | Syndicated blog