Microsoft

Blog Categories

Subscribe to RSS feed

Archives

Sign into SharePoint 2010 with Facebook using a custom STS

In an earlier post, I demonstrated how you can use Windows Azure ACS v2 as a Trusted Identity Provider to SharePoint 2010. One of the interesting options that ACS v2 provides is sign-in using Facebook. Using social media as a means to provide single sign on with SharePoint has always been an interest of mine (and I’m sure a lot of other folks as well). Curiously, I haven’t been able to find any examples out there of anyone having written an STS to enable this. Since I suspect many folks may want to in-source this kind of thing (at least initially), I figured I’d write one. This article contains all the steps necessary to do the same thing yourself. At a high level, the process is broken down as follows:

  1. Download / install the prerequisite software
  2. Configure your development workstation
  3. Create a new web site and STS
  4. Create an application in Facebook
  5. Customize the STS
  6. Test
  7. Integrate with SharePoint

DISCLAIMER: All code within this blog is intended for demonstration and proof-of-concept uses only. Use at your own risk.

And before we move on, I do want to take a second and set some expectations. This blog only addresses using Facebook as an Identity Provider to allow access to SharePoint 2010. It does not address Facebook integration with SharePoint as a whole. All that is very interesting material and it deserves a lot more treatment in a separate series of posts. Right now, I’m just going to address the very basics of signing into SharePoint. Speaking of that, it’s only my intention to introduce the community to the fundamentals of this kind of integration. It is not my intention to illustrate how to write the best, production quality STS in the world or to demonstrate a perfect understanding of the oAuth 2.0 protocol. The idea here is to help folks get started, perhaps make some mistakes, and learn some things along the way. So without further ado, here’s how it works….

STEP 1: Download / install the prerequisite software

I first installed the latest, greatest version of the Windows Identity Foundation SDK on my development workstation. The SDK includes important tools that integrate directly with Visual Studio 2010 and enable the creation of the custom STS that we’ll be using.

Secondly, I copied a C# class written by Tuyen Nguyen from Osnapz. This class is used to do the actual work of the oAuth 2.0 authentication process and will be integrated into our custom STS. It should be mentioned that this class is based on work done in 2009 by Shannon Whitley. Gotta give credit where credit is due. J

Since the Facebook API returns user data in JSON format, it would be good to have a decent mechanism to parse it. To achieve this, I used Json.NET. If you’re inclined to do this yourself, .NET 3.5 does have its own libraries for this. I opted for Json.NET partly because I heard good things about it and wanted to check it out.

STEP 2: Configure your development workstation

For this demo, I have SharePoint 2010 installed and running local on my Windows 7 laptop. All my STS work is organized into a single site called pbdev.com as shown in the following screenshot.

The pbdev.com site is explicitly bound to a dedicated IP address that I configured on a virtual network card that exists on my laptop. That way, I can bind SSL certificates to the site and use them. There may be other ways to do this, but it works for me.

And last but not least, I set the HOSTS file on my laptop to resolve pbdev.com to its assigned IP address in IIS.

NOTE: If you want to use a multi-label domain name (domain.something) for a site and want to test things out on the same machine on which it’s running, you will need to update the BackConnectionHostNames registry setting (KB926642) to permit this. If you’re unsure what to do here, implement Method 1 from the KB article and reboot before moving any further along.

STEP 3: Create a new web site and STS

This part is easy. Simply create a new site in Visual Studio 2010 (File > New > Web Site… > ASP.Net Web Site). You’ll get a standard site that is already deployed to IIS. To create the STS, just right-click the web site project and select “Add STS reference…”

This initiates the Federation Utility, which is part of the Windows Identity Foundation SDK. For now, accept the default values on the first step and select “Create a new STS project in the current solution”

By this point, you have (a) a new STS that provides basic, “Hello World” functionality and (b) a web site that is now configured as a relying party to use it. I won’t go into detail into all the things Federation Utility does in this blog, but if you want to learn more about it, I recommend checking out the Identity Developer Training Kit.

STEP 4: Create an application in Facebook

There’s not a whole lot to say here, either. You’ll need to sign into Facebook and create a new application. No special settings are required here, but you’ll need to record the Application ID and Secret when setting up the STS.

STEP 5: Customize the STS

Initially, your STS will consist of two pages: Default.aspx and Login.aspx. You will notice that default.aspx contains all the “fancy STS stuff” that references the classes that are auto-created in the App_Code folder. In most (basic) cases, this will be left alone. Login.aspx is essentially the developer’s responsibility and this is where the Facebook integration will happen.

We’ll start with configuring the Facebook integration.

  1. This first thing to do is to introduce the oAuthFacebook.cs file (see Step 1) to the project by adding it in App_Code.
  2. Next, within this class modify the CALLBACK_URL constant with a URL that matches your environment. In my case, I set it to: http://pbdev.com/oauth20_STS/login.aspx.
  3. Finally, set the _consumerKey and _consumerSecret class variables to values that match the Facebook Application ID and Application Secret respectively.

Next, we need to make updates to the code-behind for Login.aspx.

  1. Since I’m using JSON.NET to parse the JSON response from Facebook, I created a bin folder and added Newtonsoft.json.dll.
  2. I used the following code for Login.aspx.cs:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public partial class Login : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string url = string.Empty;
oAuthFacebook fbAuth = new oAuthFacebook();

if (Request["code"] == null)
{
if (Request.QueryString["ReturnUrl"] != null)
HttpContext.Current.Session.Add(“OriginalQueryString”, Request.QueryString.ToString());

//Redirect the user back to Facebook for authorization.
Response.Redirect(fbAuth.AuthorizationLinkGet());
}
else
{
//Get the access token and secret.
fbAuth.AccessTokenGet(Request["code"]);

if (fbAuth.Token.Length > 0)
{
url = “https://graph.facebook.com/me?fields=id,name,verified,picture&access_token=” + fbAuth.Token;
string json = fbAuth.WebRequest(oAuthFacebook.Method.GET, url, String.Empty);

Dictionary<string, string> claims = GetClaims(json);
HttpContext.Current.Session.Add(“OAuth20Claims”, claims);

FormsAuthentication.SetAuthCookie(“Facebook Test”, false);
Response.Redirect(“default.aspx?” + HttpContext.Current.Session["OriginalQueryString"]);
}
}
}

private Dictionary<string, string> GetClaims(string json)
{
Dictionary<string, string> claims = new Dictionary<string, string>();
JObject profile = JObject.Parse(json);

string userID = profile["id"].ToString().Replace(@””””, “”);
string name = profile["name"].ToString().Replace(@””””, “”);
string verified = profile["verified"].ToString().Replace(@””””, “”);
string picture = profile["picture"].ToString().Replace(@””””, “”);

if (!String.IsNullOrEmpty(userID))
claims.Add(System.IdentityModel.Claims.ClaimTypes.Authentication, userID);
if (!String.IsNullOrEmpty(name))
claims.Add(System.IdentityModel.Claims.ClaimTypes.Name, name);
if (!String.IsNullOrEmpty(picture))
claims.Add(System.IdentityModel.Claims.ClaimTypes.Webpage, picture);

return claims;
}

}

Here are a couple of highlights:

  • I use a session variable to store the original query string that exists when the user is first sent to the STS default.aspx by the relying party web application. This is because all the other redirects involving Facebook wipes everything out. The original query string is necessary for the redirect back to default.aspx (and all the ensuing SAML stuff) to work.
  • I generate some claims and store them into a Dictionary object, which is also stored in a session variable. This is used by the STS later on when it goes to actually create the SAML token. The way I assign these claims and values is somewhat arbitrary. For example, I’m assigning the URL for the profile picture to the “webpage” claim. Probably not a good idea to do in a production environment.
  • What you grab from Facebook is totally up to you (with the consent of the user). In this example, I explicitly state the fields that I want (id, name, verified, and picture). There are other ways to access user data documented in the API.

The last thing we need to do is update the GetOutputClaimsIdentity method within the CustomSecurityTokenService class. This is the part that reads the values assigned to the Dictionary object (session variable) earlier in Login.aspx.cs and packages them up into the SAML token. Here is the code I used to accomplish this:

protected override IClaimsIdentity GetOutputClaimsIdentity( IClaimsPrincipal principal, RequestSecurityToken request, Scope scope )
{
if ( null == principal )
{
throw new ArgumentNullException( “principal” );
}

ClaimsIdentity outputIdentity = new ClaimsIdentity();

var oAuth20Claims = HttpContext.Current.Session["OAuth20Claims"] as Dictionary<string, string>;
foreach (var openIdClaim in oAuth20Claims)
{
outputIdentity.Claims.Add(new Claim(openIdClaim.Key, openIdClaim.Value));
}

return outputIdentity;
}

And that’s pretty much it! We’re ready to test now.

STEP 6: TEST

You should be able to browse to your test web application. In my case, the URL is http://pbdev.com/oauth20/default.aspx.

If you’re not already signed into Facebook, you should be prompted to authenticate.

And a series of redirects should lead you right back to your site, which was configured to display claims values by the Federation Utility add-on we used earlier in this post.

If you can get this far, than you know your STS is working correctly and you’re ready to move on to SharePoint integration.

STEP 7: Integrate with SharePoint

There’s really nothing new here. As usual, we start with a PowerShell script to create the SPTrustedIdentityTokenIssuer in SharePoint.

$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(“c:ststestcert.cer”)
$map1 = New-SPClaimTypeMapping “http://schemas.xmlsoap.org/ws/2005/05/identity/claims/authentication” -IncomingClaimTypeDisplayName “FacebookID” -SameAsIncoming
$map2 = New-SPClaimTypeMapping -IncomingClaimType “http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name” -IncomingClaimTypeDisplayName “Display Name” -LocalClaimType “http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname”
$realm = “urn:nielski.com:facebook”
$signinurl = “http://pbdev.com/oauth20_sts/”
New-SPTrustedIdentityTokenIssuer -Name “Facebook” -Description “Facebook custom STS” -Realm $realm -ImportTrustCertificate $cert -ClaimsMappings $map1,$map2 -SignInUrl $signinurl -IdentifierClaim $map1.InputClaimType
New-SPTrustedRootAuthority -Name “Facebook custom STS token signing certificate” -Certificate $cert

There are a couple of important points I should make here:

  • If you’ve implemented any other Trusted Identity Providers in your farm, make sure the certificate specified on the first line of the above script is unique. If you do not do this, you will get incredibly unhelpful errors in PowerShell and you will not be able to move forward with configuration. If you know this will be the case and you don’t want to mess with creating a new cert, remove any pre-existing, custom Trusted Identity Providers. This is a two step process: (1) Remove the Identity Provider from each web app in Central Admin and (2) run the Remove-SPTrustedIdentityTokenIssuer in PowerShell. I got burned pretty good with this one.
  • You will notice I’m using some funky claims mappings. This is because out-of-the-box, SharePoint 2010 only supports 35 pre-defined claims and none of them are analogous to what we’re getting from Facebook. If you want to find out what those claims are, run the following command in PowerShell: (Get-SPClaimProviderManager).TrustedClaimProviderMappableClaims.
  • You can extend SharePoint to use custom claim types that you define. Steve Petschka wrote a fabulous tutorial on how to create a custom Claims Provider to accomplish this.
  • The value you assign to $realm is very important. Be sure to mark this down because we’ll need it for one final modification to our custom STS.

The next thing we need to do is add our new Trusted Identity Provider to your web app in Central Admin.

Next, we need to sign into our web application using an administrator account. For starters, I recommend just granting any user validated by the Facebook Trusted Identity Provider read access. To do this, I went to my site collection “visitors” group and added a new member using the “New” button. I then clicked the user picker (little dictionary icon) at the Grant Permissions dialog.

In the “Select People and Groups” window, just click the blue magnifying glass. You will then see an option for “All Users (Facebook)”.

That’s it. We just completed the SharePoint configuration.

We have one final update to make, and it’s on the STS. Basically, we’ll need to instruct the STS where to redirect the user back to when the SAML token is created. In WS-Federation, the protocol we’re using with SharePoint 2010, this is referred to as the ‘wreply’ attribute. The value is defined in the GetScope method located within the CustomSecurityTokenService class (see line 113). The web application URL in my lab is “nielski.com”, so I defined some (real) basic logic in that method to redirect anyone who is using the realm of “urn:nielski.com:facebok” to be redirected back to the correct /_trust/ endpoint on my web app. This is what the code looks like.

if (scope.AppliesToAddress == “urn:nielski.com:facebook”)
{
scope.ReplyToAddress = “http://nielski.com/_trust/”;
}
else
{
scope.ReplyToAddress = scope.AppliesToAddress;
}
return scope;

Supplying this is mandatory. Note that you could set the value for $realm (aka “AppliesToAddress”) in the PowerShell script (above) to “http://nielski.com/_trust/” and you wouldn’t have to do anything here. But I prefer to keep the realm name and the actual endpoint to be separate.

Now the fun part. Sign-out of your web application and sign back in. You should now see SharePoint “Sign In” page. Simply select “Facebook”.

If you’re not signed into Facebook already, you will be redirected to the Facebook login page. After authenticating, you’ll get redirected back to SharePoint with read access.

Tags: , , , ,

Leave a Reply