Tagged: SharePoint 2013

Create a custom SharePoint 2013 Geolocation field using Nokia HERE maps

NOTE: The HERE Maps API for JavaScript 2.x used in the post below will be phased out by September 30, 2015. For more information about the 3.x version, please visit https://developer.here.com/javascript-apis.

Much has been written about the new Geolocation capabilities of SharePoint 2013. If you haven’t already, I would highly recommend reading this overview on MSDN. In short, SharePoint 2013 offers a new field type called Geolocation, which represents point location data (latitude and longitude) that can be automatically plotted on a Bing map.

But what if you want to leverage a different mapping provider, such as Nokia HERE? Fortunately, SharePoint offers the ability to define custom field types based on an existing field type (in this case, SPGeolocationField). Using SharePoint 2013’s new client-side rendering framework, it is possible to define custom rendering logic for your custom field type using JavaScript.

Microsoft provides sample code that allows you to create a Geolocation field that renders using Nokia maps. The custom field type uses JavaScript to render a text box with a search button that performs a geocoding request on the inputted text string using the Nokia Places API. Once a coordinate location has been returned, a HERE map is rendered with a placemark centered on the inputted location. Unfortunately, the sample code has not been updated since February 2013 and a few changes need to be made in order to make use of Nokia’s new HERE mapping platform. If you would like to make use of this sample code, hopefully this post will help you!

Step 1 – Register a HERE developer account

In order to use HERE maps in your application, you need to first sign up for an account on the HERE developer portal. Once you have registered, go to Create app. Specify the following:

Application platform: Web
Solution: Web Experiences
License type: Platform Evaluation Key

step1

Press Proceed to application details. Specify details about your application, including the Application website URL (the domain name where SharePoint is running). After you provide your contact information and accept the license agreement, you will see a confirmation screen with your App_Id and App_Code values:

appid

Step 2 – Update the sample project

When I first opened the sample project, I was prompted to upgrade it to a SharePoint 2013 solution. After upgrading the project, I had to manually add NokiaMapsControl.js and fldtypes_NokiaMapsControl.xml back to the project (NOTE: These files should be placed directly under the mapped {SharePointRoot}\TEMPLATE\LAYOUTS and {SharePointRoot}\TEMPLATE\XML directories and NOT in subfolders.) The project should now look like this:

solution

Step 3 – Update NokiaMapsControl.js

Make the following changes/updates to NokiaMapsControl.js:

1. In line 1, update the g_appId variable with the App_Id value from your application from the HERE developer portal.
2. In line 2, update the g_authToken variable with the App_Code value from your application from the HERE developer portal.
3. Replace line 138 with the following two lines, which load the new HERE Places API and JavaScript API:

downloadJS('http://js.api.here.com/se/2.5.3/jsPlacesAPI.js');
downloadJS('http://js.api.here.com/se/2.5.3/jsl.js');

4. Update lines 240-241 with the following (the HERE API parameter names have changed from appId and authenticationToken to app_id and app_code, respectively):

nokia.Settings.set("app_id", g_appId);
nokia.Settings.set("app_code", g_authToken);

Step 4 – Build and deploy the project, then add the new field to a list

After deploying the project, add a new column based on the custom field type to a list:

AddColumn

Step 5 – Add a new item to the list

The Nokia Maps Control field renders as a text box with a search button. Enter a location in the text box and press the Search button:

newitem

The HERE geocoder service will return a coordinate location for the inputted string, plotted on a HERE map:

newitem2

Congratulations! You are now ready to use your custom geolocation field with Nokia HERE maps.

You can download a version of the project with all the necessary changes here. Just update lines 1 and 2 of NokiaMapsControl.js with your App_Id and App_Code values and you’ll be good to go!

Set the Bing maps API key in Office 365 using CSOM to leverage SharePoint 2013 Geolocation features

Much has been written about the new Geolocation capabilities of SharePoint 2013. If you haven’t already, I would highly recommend reading this overview on MSDN. In short, SharePoint 2013 offers a new field type called Geolocation, which represents point location data (latitude and longitude) that can be automatically plotted on a Bing map.

In order to take full advantage of the built-in Bing mapping functionality, you must obtain an API key from the Bing Maps portal. Different API key types are available depending on your needs (Trial keys work great for demos and development environments, while Basic or Enterprise keys may better suit your needs in a production environment).

If you attempt to leverage a Geolocation column in SharePoint 2013 (on-premises or in Office 365) without obtaining and setting a Bing Maps API key, you will receive a subtle reminder that you need to do so:

noapikey

In an on-premises environment, you can use the PowerShell Set-SPBingMapsKey cmdlet to set the Bing maps API key at the farm level. However, it is not possible to use this cmdlet in an Office 365 environment. Fortunately, the Bing maps API key is also stored in the property bag of an SPWeb (with key “BING_MAPS_KEY“). We can set this value via .NET or JavaScript using the client object model.

To set the API key I obtained for my Office 365 site, I used the following code in a simple .NET console application:

        static void Main(string[] args)
        {
            var webUrl = new Uri("");
            using (ClientContext ctx = new ClientContext(webUrl))
            {
                var login = "";
                var password = "";
                var secureStrPwd = new SecureString();
                foreach (char c in password)
                {
                    secureStrPwd.AppendChar(c);
                }

                var creds = new SharePointOnlineCredentials(login, secureStrPwd);
                ctx.Credentials = creds;

                var web = ctx.Web;
                web.AllProperties["BING_MAPS_KEY"] = "";
                web.Update();

                ctx.ExecuteQuery();
            }
        }

Once the API key has been set, your maps will render without that annoying message!

apikey

Customize the Default #WIF ASP.NET #STS to Support Multiple #SharePoint Web Applications

Thanks to Claims-based identity in SharePoint 2010 and 2013, we have the ability to develop our own trusted login providers. These providers take the form of a Security Token Service (STS), an ASP.NET web site that packages and signs SAML tokens containing claims. SharePoint is then configured to trust this STS. In this blog post, I walk through the steps required to develop a custom STS using the Windows Identity Foundation (WIF) SDK 4.0 that supports LinkedIn as an identity provider for SharePoint. However, there is one significant shortcoming with the approach I use in that post: it can only be used for a single SharePoint web application, intranet.contoso.com. This is due to two factors:

  • The realm used by the SPTrustedIdentityTokenIssuer is configured as the URL of SharePoint’s STS within that web application (e.g., intranet.contoso.com/_trust).
  • The default behavior of the WIF STS web site is to forward tokens for authenticated users to the specified realm URL. From there, SharePoint’s STS will forward the logged in user back to the page he or she was trying to access.

If you tried to use this trusted identity provider on another web application (such as hr.contoso.com or finance.contoso.com), you would end up disappointed as your users would be forwarded back to the intranet.contoso.com web application no matter which site they were actually trying to access!

In this blog post, I will make some slight modifications to the out-of-the-box WIF STS web site so that:

  • The same trusted identity provider may be used across multiple different web applications, each with different host header URLs.
  • The SPTrustedIdentityTokenIssuer does not need to be updated and can leverage the same realm value no matter how many web applications make use of it.
  • The WIF STS web site I build and customize does not need to be updated and can leverage the same realm value no matter how many web applications make use of it.

What is a realm?

The default behavior of the WIF STS web site is to forward an authenticated user back to the URL specified as the realm. According to MSDN, the realm value is “used by the STS to identify the relying party instance and to select the corresponding token issuance policy and encryption certificate.” The realm is specified as a URI (which can actually be a URL or a URN), but in many walkthroughs (mine included), we specify a value for the realm that is tied to a specific host header URL (e.g., intranet.contoso.com/_trust).

Decoupling the realm from a specific host header URL

In an environment where only one web application needs to use a trusted identity provider, it may make sense to associate the realm with the URL of that web application. But in an environment where multiple claims web applications in a SharePoint farm need to use that same trusted identity provider, a different approach must be taken. As Steve Peschka describes in this post, we can use a URN (e.g., urn:linkedin:sts) instead of a URL to work around this limitation. In the case of ADFS v2 (disclaimer: I have not worked with ADFS v2), you can define mappings between these URNs and their associated web applications as Steve describes in the blog post linked above.

Updating the default WIF STS web site

If I change my SPTrustedIdentityTokenIssuer to use the generic realm urn:linkedin:sts, but make no changes to my STS web site, what will happen? As it turns out, I will be redirected to this URN, which of course is not the URL I want my logged in users to visit. Hilarity ensues:

sts404

As you can see, the STS redirects the logged in user to urn:linkedin:sts, which of course produces a 404 error.

The key to having the STS redirect logged in users to the proper URL is in the CustomSecurityTokenService class, which contains a function GetScope() that sets the ReplyToAddress of the SecurityTokenService.Scope object. The default STS web site implementation uses the following code:

// Set the ReplyTo address for the WS-Federation passive protocol (wreply). This is the address to which responses will be directed.
// In this template, we have chosen to set this to the AppliesToAddress.
scope.ReplyToAddress = scope.AppliesToAddress;

The AppliesToAddress value is determined from the wtrealm parameter specified as the “OAuth Accept Redirect URL” in our LinkedIn application configuration. Remember that we had previously set this value to intranet.contoso.com/_trust (which would work as a URL), but have since updated it to urn:linkedin:sts.

How can I fix this?

The way you choose to work around this behavior depends on your requirements. If you need to use different token encrypting certificates based on the relying party (RP) application, or if you want to potentially execute any custom business logic specific to some (but not all) of your RP applications, you may choose to update the code in the GetScope() function to look more like the following:

if (scope.AppliesToAddress == "urn:linkedin:sts")
{
    scope.EncryptingCredentials = new X509EncryptingCredentials(CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "EncryptingCert1"));
    scope.ReplyToAddress = "http://intranet.contoso.com/_trust";
    // More things specific to the intranet web application and the urn:linkedin:sts realm...
}
else if (scope.AppliesToAddress == "urn:someother:sts")
{
    scope.EncryptingCredentials = new X509EncryptingCredentials(CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, "EncryptingCert2"));
    scope.ReplyToAddress = "http://extranet.contoso.com/_trust";
    // More things specific to the extranet web application and the urn:someother:sts realm...
}

This approach carries the following “baggage” (which may be completely necessary based on your requirements):

  • The STS has to be recompiled and redeployed anytime a new web application needs to be supported.
  • It still cannot be reused across any host-header named web applications.

But what if I don’t have any special requirements? What if I just want my STS to redirect authenticated users back to whichever web application referred them, even new web applications that I don’t know about yet? We can do this! The way I chose to accomplish this is as follows:

  • In Login.aspx.cs of my STS web site (which is the first page a user will hit prior to being authenticated), I can grab the host header from the UrlReferrer property of HttpContext.Current.Request and store it in a session variable with /_trust appended. This will serve as the realm value I want my STS to use and will contain the value of the referring SharePoint web application.
  • In Default.aspx.cs of my STS web site, see if the session variable I created above exists. If it does, pass it in to the constructor for my CustomSecurityTokenService class.
  • In App_Code/CustomSecurityTokenService.cs, store the updated realm value in a member variable. If the STS was called with a realm value of urn:linkedin:sts (some minimal validation of the RP), redirect the user to the appropriate realm URL in the GetScope() function.

Here are the relevant code snippets required to accomplish this:

Login.aspx.cs:

Uri referrer = HttpContext.Current.Request.UrlReferrer;
if (referrer != null)
{
    string defaultRealm = referrer.ToString();
    // From SharePoint, this value will be:
    defaultRealm = defaultRealm.Substring(0, defaultRealm.IndexOf("_login")).TrimEnd('/') + "/_trust/";
    Session["defaultRealm"] = defaultRealm;
}

Default.aspx.cs:

// Process signin request.
SignInRequestMessage requestMessage = (SignInRequestMessage)WSFederationMessage.CreateFromUri( Request.Url );

string defaultRealm = "";
if (Session["defaultRealm"] != null)
{
    defaultRealm = Session["defaultRealm"] as string;
}

if ( User != null && User.Identity != null && User.Identity.IsAuthenticated )
{
    SecurityTokenService sts = new CustomSecurityTokenService( CustomSecurityTokenServiceConfiguration.Current, defaultRealm );
    SignInResponseMessage responseMessage = FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest( requestMessage, User, sts );
    FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse( responseMessage, Response );
}

App_Code/CustomSecurityTokenService.cs:

private string defaultRealm = string.Empty;

public CustomSecurityTokenService( SecurityTokenServiceConfiguration configuration, string realm = "" )
    : base( configuration )
{
    defaultRealm = realm;
}

protected override Scope GetScope( IClaimsPrincipal principal, RequestSecurityToken request )
{
    ValidateAppliesTo( request.AppliesTo );

    Scope scope = new Scope( request.AppliesTo.Uri.OriginalString, SecurityTokenServiceConfiguration.SigningCredentials );

    string encryptingCertificateName = WebConfigurationManager.AppSettings[ "EncryptingCertificateName" ];
    if ( !string.IsNullOrEmpty( encryptingCertificateName ) )
    {
        // Important note on setting the encrypting credentials.
        // In a production deployment, you would need to select a certificate that is specific to the RP that is requesting the token.
        // You can examine the 'request' to obtain information to determine the certificate to use.
        scope.EncryptingCredentials = new X509EncryptingCredentials( CertificateUtil.GetCertificate( StoreName.My, StoreLocation.LocalMachine, encryptingCertificateName ) );
    }
    else
    {
        // If there is no encryption certificate specified, the STS will not perform encryption.
        // This will succeed for tokens that are created without keys (BearerTokens) or asymmetric keys.
        scope.TokenEncryptionRequired = false;
    }

    // Set the ReplyTo address for the WS-Federation passive protocol (wreply). This is the address to which responses will be directed.
    // In this template, we have chosen to set this to the AppliesToAddress.
    if (scope.AppliesToAddress == "urn:linkedin:sts")
    {
        scope.ReplyToAddress = defaultRealm;
    }
    else
    {
        scope.ReplyToAddress = scope.AppliesToAddress;
    }

    return scope;
}

Putting it all together

After deploying this updated STS, any new SharePoint web application I create that leverages the LinkedIn trusted identity provider will redirect users back to the appropriate URL after they are authenticated, regardless of whether the new web applications use a different host header, port, or both. To prove this, I created a new web application on port 39668 and set a “Full Read” user policy for all LinkedIn users. Upon accessing the site, I choose to sign in using my LinkedIn identity provider:

newsts1

newsts2

After supplying my credentials, LinkedIn will redirect me back to my STS. My STS will then determine where to redirect me based on the code above, and voila, I will be taken to my new SharePoint site, authenticated and signed in!

newsts3

JavaScript to Conditionally Enable a SharePoint Custom Ribbon Button

During my session on getting started with apps for SharePoint 2013, I demonstrate how to use the “extension” app shape to define a UI custom action that displays a button in the ribbon. When clicked, this button allows the user to view a Bing traffic map centered at the location specified in the selected list item. This is done using a CustomAction in the same declarative manner as it was done in SharePoint 2010, using the following XML:

customaction1

Installing the app adds a button labeled “Plot on Bing Traffic Map” to the “Items” tab on all custom lists in the host web where the app is installed:

button0

In my CommandAction of the CommandUIHandler for the CommandUIExtension I define to create the button, I specify that clicking the button will take the user to the following URL:

~appWebUrl/Pages/BingTrafficMap.html?{StandardTokens}&ListID={ListId}&ItemID={SelectedItemId}

Note the use of the SelectedItemId token in the URL above. This means my BingTrafficMap.html page can rightly expect to have an ItemID parameter in its query string, which can then be parsed to get the list item and read its properties using JavaScript.

But what if the user hasn’t selected any items? Or multiple items? Since I can only center a map on one single location, I need to have more control over when this button is enabled. In this case, I only want the button to be enabled when exactly one item in the list is selected. (By default, any ribbon buttons you create through custom actions will always be enabled, regardless of whether or not any items are selected.)

As it turns out, there is an EnabledScript attribute of the CommandUIHandler that will enable or disable the button depending on whether the function returns true or false. I was able to adapt this code from Tomasz Rabiński to add the following logic to my CommandUIHandler:

customaction2

Note that the function EnableDisable–which I can define and then call within the EnabledScript attribute–returns true only if the number of selected items equals 1.

Now my button behaves exactly as I would expect.

No items selected (disabled):

button1

One item selected (enabled):

button2

More than one item selected (disabled):

button3

I have updated my demo code to include this change. You can download this code here.

Presenting Apps for SharePoint 2013 at @CapAreaSP!

Thanks to everyone who braved the elements (e.g., the winter storm that never materialized) to see me present “Get Started with Apps for SharePoint 2013!” at the Capital Area .NET SharePoint Special Interest Group at Knowlogy in Tysons Corner tonight! I was very impressed with the level of audience participation and interaction during the session. Feel free to reach out to me anytime (either in the comments here or on Twitter) if you have any questions or just want to chat about Apps for SharePoint 2013!

Slides and code from my session

Code for the sample app I demonstrated can be downloaded here.

To learn more about when and when not to use the new app model for SharePoint 2013 development, attend next month’s @CapAreaSP session featuring Alex Randall from Microsoft!

Kicking off @RestonSPUG with Apps for SharePoint 2013!

Thanks to everyone who joined me, Scott Hoag, and Dan Usher as we kicked off the new Reston SharePoint User Group today! The group will meet around lunchtime at the Microsoft office in Reston on the first Monday of each month. The group’s meeting time and location should be ideal for the many SharePoint professionals in the Reston area who can’t make it to the various regional SPUGs that traditionally meet in the evenings.

I had the privilege of being the group’s inaugural speaker today, presenting my session “Get Started with Apps for SharePoint 2013!” Much like the session I led last weekend at SPS Virginia Beach, there was great audience participation and interaction. There were a couple of great questions in particular that I wanted to address further here:

If an app requests the Write right at the List scope, is there any way I can install or configure multiple instances of the app to grant it write permissions against two or more lists on a site?

This was an interesting question because the guidance I gave last weekend was that in order to grant the Write right against multiple lists, your app must request permissions at the next highest scope (in the case of this example, that would be the Web scope). However, it is not possible to install multiple instances of an app for SharePoint to the same site. The UI will not show the app in the “Apps you can add” section for that site once an instance of that app is already installed. I even tried forcing my around that by using PowerShell, but was forbidden:

powershell

What is the relationship between OAuth and tokens with apps for SharePoint? Is there a token for the user as well as one for the app that factors into authorization decisions?

The OAuth protocol is used to authenticate and authorize apps for SharePoint. Authentication and authorization of users and apps in SharePoint 2013 is a very advanced subject with many potentially complicated scenarios and unique considerations. To learn more about these topics, I strongly encourage you to read the following articles on MSDN:

Slides and code from my session

Code for the sample app I demonstrated can be downloaded here.

Thanks again to everyone who was part of today’s Reston SPUG event. I look forward to working with the group much more in the future!

Get Started with Apps for SharePoint 2013! #SPSVB 2013 Recap

Thanks to everyone who attended my session “Get Started with Apps for SharePoint 2013!” at SPSVB yesterday! We had some great discussions and questions about the new app model for SharePoint 2013, and a few questions came up during the session that I wanted to address in more detail here.

How are apps for SharePoint deployed in a production environment?

We demonstrated how to create a new App for SharePoint project in Visual Studio 2012 (where the app is configured to be automatically deployed to a specific site for debugging). But how do developers hand off an app package for a SharePoint-hosted app to be deployed in production?

Visual Studio will generate the .app file in an app.publish subdirectory of the bin\Debug, bin\Release, etc. directory of your project. Assuming your system administrators have configured an app catalog site (these are configured at the web application level), anyone with permissions to the Apps for SharePoint library (automatically configured on the app catalog site) can upload the .app file to make the app available to all sites within that web application.

Uploading the file here:

file

To the library here:

newapp

Makes the app available to be installed on any site within the web application:

addapp

Olivier Carpentier has also written .NET code and PowerShell scripts to install apps for SharePoint, if that is more your speed.

How do you grant an app the Write right on two or more lists?

In our demo, we had our app request the Write right at the List scope. This corresponds to the following entry in the app’s AppManifest.xml:


  

When the app is installed, the dialog presented to the user allows the selection of a single list to which the app will be granted Write permissions:

trustdialog

As you can see, however, this dialog only enables me to select a single list. What if we want our app to be able to write to two or more lists? Unfortunately, there is no mechanism to do this right now. If we want our app to be able to write to multiple lists, we must request the right at the next highest scope (in this case, the Web). This corresponds to the following entry in the app’s AppManifest.xml:


  

Unfortunately, this will grant your app permission to write to all lists within the host web where the app is installed.

How can apps for SharePoint support workflow?

The Workflow object model has been greatly enhanced in SharePoint 2013. Among these enhancements include a client object model (CSOM), JavaScript object model (JSOM), and REST-based APIs for workflow. In a SharePoint-hosted app, the JSOM or REST APIs can be used to access the Workflow object model without any server-side code. You can download sample code for a Workflow-powered app for SharePoint from MSDN.

That said, workflow should only be used in an app for SharePoint when the workflow is integral to the app itself. SharePoint Designer 2013 is the recommended tool for authoring workflows in SharePoint 2013.

Can web part connections be set up on .aspx pages within immersive, full-page apps?

Obviously no server-side code is allowed in a SharePoint-hosted app, but every app must contain at least one page (the app’s Start page). This page may be an .aspx page (referencing server controls, but containing no server-side code). Can we connect web parts on this page? Thus far, I have not been able to do this. I even tried following a similar approach to the one outlined here where the connection between two web parts is provisioned declaratively, but to no avail. If anyone out there can shed any additional light on this question, please feel free to leave a comment!

Slides and code from my session

Code for the sample app I demonstrated can be downloaded here.

In conclusion, thank you!

Thanks again to all the attendees, volunteers, sponsors, speakers, and organizers who continue to make these SPS Events so great, and a special thanks to Microsoft consultant Alex Randall who helped answer many of the questions I had about apps for SharePoint 2013 during and after his session yesterday, When and When Not to Use the New App Model.

Happy app development!

Using Azure ACS to Sign In to SharePoint 2013 with Facebook

For those of you who have seen me speak (or read my blog posts) about Claims-based identity, SharePoint 2010, Facebook, and the cloud, you already know that Windows Azure Access Control Service (ACS) can be used to set up identity providers such as Windows Live ID, Google, Yahoo!, and Facebook in SharePoint 2010 through the magic of Claims.

In this blog post, I am pleased to report that all the steps to follow to enable this integration with SharePoint 2010 also work with SharePoint 2013!

For those who are unfamiliar with this integration, I will cover the steps at a high-level below with screenshots from SharePoint 2013. Keep in mind the process to follow is the same for SharePoint 2010.

Prerequisites

Procedures

Setting up this integration requires configuration steps to be performed in three different places:

  1. Within Facebook, an application must be created that supports “Website with Facebook Login.”
  2. Within the Azure ACS management portal, a new Identity Provider (IP), Relying Party (RP) application, and Rule Group must be created to inform Azure ACS about:
    a. The Facebook application created above.
    b. The SharePoint environment to be configured with Azure ACS integration below.
  3. Within SharePoint, we must create a new web application with Claims (in SharePoint 2013, Claims is the default authentication mechanism) and configure it to point to our Azure ACS setup as a Trusted Identity Provider.

We must also inform both Azure ACS and SharePoint about the SSL certificate that will be used to sign the SAML tokens containing a user’s claims. This is done by uploading the X.509 certificate into the Azure ACS management portal and telling SharePoint to trust this certificate (via the New-SPTrustedRootAuthority and New-SPTrustedIdentityTokenIssuer cmdlets which we will execute later on). In a demonstration environment, I use a self-signed certificate made via the makecert command. In production, you would obviously want to use a legitimate SSL certificate.

Facebook Application Setup

Within the Facebook Developers application, click Create New App.

Give the app a Name and a Namespace. Click Continue.

After passing the Captcha check, select Website with Facebook Login in the next screen and enter the URL to your Azure ACS Service Namespace (e.g., https://{your namespace}.accesscontrol.windows.net). Click Save Changes.

Take note of the App ID and App Secret values that appear at the top of this screen. You will need to use these to configure Azure ACS to leverage this application. That’s all we need to do within Facebook!

Azure ACS Setup

Within Azure ACS, we must configure the following four things:

  1. Facebook as an Identity Provider.
  2. SharePoint as a Relying Party Application.
  3. Claims Rule Groups to determine how Claims are passed from the identity provider to the relying party application.
  4. The Token Signing Certificate that Azure ACS will use to prove that it is indeed the issuer of the SAML token that SharePoint receives.

Identity Provider

From within your Azure ACS management portal (e.g., https://{your namespace}.accesscontrol.windows.net) and select Identity providers from the Trust relationships section in the left navigation. In the next screen, click Add.

In the next screen, choose Facebook application and click Next.

In the next screen, enter the Application ID and Application secret values from the Facebook application you created above. You should also provide a Display name (for use within the ACS management portal) and a comma-separated list of Application permissions (note that email is the only required permission to enable Facebook users to sign in to SharePoint). You can, however, request additional permissions to do lots of fun and exciting things. Those permission strings are defined here.

You do not need to specify values for Login link text or Image URL unless you plan to configure more than one Azure ACS identity provider to use with SharePoint. If you have already configured your Relying party applications within Azure ACS, you may select them at the bottom of this screen. Otherwise, we will configure SharePoint as an RP in the next step.

Press Save to save changes.

Relying Party Application

From within your Azure ACS management portal (e.g., https://{your namespace}.accesscontrol.windows.net) and select Relying party applications from the Trust relationships section in the left navigation. In the next screen, click Add.

In the next screen, provide a name for the relying party application (I often just use the fully-qualified domain name of my SharePoint web application) and choose to Enter settings manually. In the boxes below, enter the following values:

  • Realm – URL of your SharePoint web application (note that a URN can also be entered here and, in many cases, is the preferred approach)
  • Return URL – URL of your SharePoint web application + /_trust – this is the endpoint for SharePoint’s STS, which is where Azure ACS will send the SAML token it creates
  • Token format – SAML 1.1
  • Token lifetime – enter a value greater than the default 600 seconds (Wictor Wilen gives a great explanation why here)

In the Authentication Settings section, select the Identity provider you configured above and choose to Create a new rule group. Under Token Signing Settings, choose whether to Use service namespace certificate (if you have already configured a certificate within Azure ACS) or Use a dedicated certificate if you would like to use a different X.509 certificate exclusively for this relying party application.

Click Save to save changes.

Rule Group

From within your Azure ACS management portal (e.g., https://{your namespace}.accesscontrol.windows.net) and select Rule groups from the Trust relationships section in the left navigation. In the next screen, click Default Rule Group for {your web application}.

Note that no rules are added by default. Click Generate and select the identity provider you created above.


Click Generate to generate Claims rules for the 5 values Azure ACS can obtain from a logged in Facebook user:

  1. AccessToken – the Facebook Graph API access token
  2. emailaddress – the email address associated with the user’s Facebook profile
  3. expiration – the expiration date/time of the AccessToken granted above
  4. name – the Facebook user’s display name
  5. nameidentifier – the Facebook user’s unique profile ID (integer)

Press Save to save the rules.

Upload Token Signing Certificate

If you haven’t already, you will need to configure Azure ACS to utilize an X.509 certificate to digitally sign the tokens it generates. Optionally, you can also specify certificates to use for token encryption and decryption. For the purposes of this demonstration, I generated a self-signed certificate using the makecert utility. DO NOT DO THIS IN PRODUCTION! I then uploaded this certificate by going to the Certificates and Keys link under Service settings in the ACS management portal.

cert1

Click Add to upload your certificate. This page allows you to specify where the certificate should be used, what type of certificate it is, and how to make it the primary token-signing certificate. It even includes the specific makecert command you need to run to generate a self-signed certificate (again, I cannot overemphasize how important it is that you NOT use a self-signed certificate in production!)

cert2

That’s all we need to do within Azure ACS!

SharePoint 2013 Setup

Within SharePoint, we must do the following:

  1. Create a new web application using Claims-based authentication (the default mechanism in SharePoint 2013).
  2. Configure Azure ACS as a new Trusted Identity Provider for that web application and tell SharePoint to trust the certificate Azure ACS uses to sign its SAML tokens.
  3. Set a User Policy within the web application to allow users who log in via the Trusted Identity Provider to have access to the site.

New Web Application

From SharePoint 2013 Central Administration, create a new web application. Unlike SharePoint 2010, SharePoint 2013 does not prompt you to choose between Classic and Claims-based authentication. Claims is the default (hooray)!

Make sure the host header matches the host header for the Return URL specified in the Azure ACS relying party application setup. Enable Integrated
Windows Authentication with NTLM at this step. You’ll notice no Trusted Identity providers exist at this point.

Enjoy the friendly new progress messages in SharePoint 2013:

Create a new site collection at the root of the web application and choose a Windows identity for the primary Site Collection Administrator.

Configure Azure ACS as a new Trusted Identity Provider

In order to enable us to select Azure ACS as a trusted identity provider for the web application, we need to run some PowerShell. The script to run appears below.

$realm = "http://intranet.contoso.com"
$signinurl = "https://dannyjessee.accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=http%3a%2f%2fintranet.contoso.com%2f"
$certloc = "C:\dannyjessee.cer"
$rootcert = Get-PfxCertificate $certloc
New-SPTrustedRootAuthority "Danny Jessee Azure ACS" -Certificate $rootcert
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certloc)
$map1 = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" -IncomingClaimTypeDisplayName "Email" -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"
$map3 = New-SPClaimTypeMapping -IncomingClaimType "http://www.facebook.com/claims/AccessToken" -IncomingClaimTypeDisplayName "Access Token" -SameAsIncoming
$map4 = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" -IncomingClaimTypeDisplayName "Name Identifier" –LocalClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"
$map5 = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.microsoft.com/ws/2008/06/identity/claims/expiration" -IncomingClaimTypeDisplayName "Expiration" -SameAsIncoming
New-SPTrustedIdentityTokenIssuer -Name "Facebook" -Description "Facebook" -Realm $realm -ImportTrustCertificate $cert -ClaimsMappings $map1,$map2,$map3,$map4,$map5 -SignInUrl $signinurl -IdentifierClaim $map1.InputClaimType

This script performs the following actions:

  1. Establishes a trust relationship between SharePoint and the Azure ACS token signing certificate (this is the equivalent of going to Security > Manage trust and uploading this certificate in Central Administration).
  2. Sets up the claim rules to pass the claims returned from Azure ACS to the SharePoint STS at the _trust endpoint (in some cases, these claims need to be mapped to different claim type URIs to avoid collisions with claims provided by the SharePoint STS).
  3. Creates a new trusted identity provider associated with the appropriate realm, certificate, claims mappings, and sign in URL (this value can be obtained by going to Application integration under Development in the Azure ACS management portal, then selecting Login pages).

Some things to keep in mind as you modify this script to run in your environment:

  • Ensure the value for $realm matches the realm value used when creating the relying party application within Azure ACS.
  • Ensure the X.509 certificate used here is the same as the token signing certificate used when creating the Relying Party application within Azure ACS.
  • Ensure the value for $signinurl is set properly for your SharePoint web application.

Run this PowerShell script from the SharePoint 2013 Management Shell (as an Administrator). If you don’t see red text, you’re doing alright!

Return to the list of web applications in SharePoint 2013 Central Administration. Select the web application and press Authentication Providers.

Choose the appropriate zone and scroll down. Facebook should now appear in the list of trusted identity providers:

Select Facebook and press Save. You have now configured Azure ACS as a new trusted identity provider, and SharePoint knows it can trust SAML tokens signed with your Azure ACS token-signing certificate.

Set User Access Policy

In order for users to access your SharePoint 2013 site once they have authenticated via Facebook, we must grant them the appropriate level of authorization. To do this, I recommend setting a “Full Read” policy for all users who authenticate to SharePoint via our “Facebook” trusted identity provider. Start by selecting the web application in Central Administration and go to User Policy.

Choose Add Users, then select the appropriate zone (All zones) and press Next.

Select the address book icon beneath the Users text box to bring up the Select People and Groups dialog.

Select All Users, then choose All Users (Facebook). Press Add to select the group.

Check the box for Full Read in the permissions section and press Finish.

The new policy is now displayed at the bottom of the list.

With that, our SharePoint configuration is complete! We are now ready to have our users sign in to SharePoint 2013 with Facebook.

Signing in to SharePoint 2013 with Facebook

Navigate to the home page of the web application. The default sign in page will appear.

Choose Facebook from the drop down list. The user will be redirected (through the Azure ACS portal) to a Facebook-hosted login page.

Enter your Facebook credentials and press Log In. The first time a user attempts to log in to your SharePoint site with Facebook, he or she will be prompted to grant the Facebook application access to the user’s basic information and email address (this is based on the permissions we set up when we initially defined the Facebook identity provider in the Azure ACS management portal).

Press Go to App. The user should be redirected back to Azure ACS, which then redirects the user back to SharePoint…logged in with Facebook credentials!

Note the user’s display name is the email address associated with the user’s Facebook account. This is because we set EmailAddress as the IdentifierClaim in the PowerShell script we ran to configure Azure ACS as a trusted identity provider.

I know this is a TON of information to put in a single blog post, so feel free to post any questions in the comments!

Using LinkedIn as an Identity Provider for SharePoint 2010

Updated 5/29/2013: LinkedIn has changed the format of the profile URLs it sends back. Consequently, I had to change the GetProfileId function in my STS code. The code in this post has been updated. Thanks to Dee for commenting and bringing the issue to my attention!

Updated 1/27/2013: I am pleased to report that the steps below also work for setting up LinkedIn as an identity provider for SharePoint 2013! The SharePoint screens will look a little different, but the steps to set up the integration are the same. If you run into any issues with this setup in SharePoint 2013, please leave a comment at the bottom of this post.

Updated 1/22/2013: After almost a year, I thought I would go back and revisit this post (as it continues to be one of the most popular posts on my blog). As it turns out, some updates to the code were required and many of the LinkedIn user interfaces required to complete this integration had changed. I can verify that the steps below work properly and that all screenshots are current as of January 22, 2013. Please leave a comment on this post if you run into any issues.

Those of you who have seen me speak at a user group or SharePoint Saturday event recently know how much I love Windows Azure AppFabric’s Access Control Services and how easy ACS makes it to configure SharePoint 2010 to allow users to log in with OpenID identity providers such as Facebook, Google, Yahoo!, and Windows Live. But what about LinkedIn? While LinkedIn isn’t an OpenID provider per se, numerous sites on the web allow users to sign in using their LinkedIn account credentials via OAuth. When the subject of external identity providers for a future version of the SharePoint User Group of Washington, DC site came up during my recent presentation there, the audience overwhelmingly agreed that LinkedIn was the most “professional” identity provider to integrate (and certainly one that most SharePoint professionals would feel more comfortable using than Facebook). There was only one problem…Azure AppFabric ACS does not natively support LinkedIn as an identity provider. What was I to do?

As one astute observer pointed out during my presentation, ACS is not required to configure SharePoint 2010 to interact with any external identity provider. While ACS greatly simplifies the management and configuration required to set up an external identity provider and its associated claim rules, it is possible to write code that leverages a custom STS (Security Token Service) to manage all of this without involving Azure at all.

Getting Started

Luckily for me, the vast majority of the technical “heavy lifting” required to accomplish this integration had already been done by Travis Nielsen. In this blog post, Travis details the steps (and development prerequisites, including the Windows Identity Foundation SDK 4.0) required to integrate SharePoint 2010 with Facebook as an identity provider using a custom STS (and without using Azure ACS). The steps for integrating LinkedIn are basically the same as they are for Facebook. Below I will detail how configuring the two identity providers differs.

C# OAuth Class for LinkedIn

I was preparing to have to adapt the Facebook OAuth class Travis had found to work with LinkedIn. Fortunately, I stumbled upon a C# OAuth class for LinkedIn that had already been developed by Fatih YASAR. All credit for this aspect of the solution belongs to him.

Creating the Application

Much like configuring Facebook as an identity provider requires the creation of an “application” within Facebook, LinkedIn requires the creation of an application as well.

Anyone with a LinkedIn account can create an application through the LinkedIn Developer Network.

1. Go to https://www.linkedin.com/secure/developer.

lidn

2. Click Add New Application. Fill out the form to register your new application. Any values you enter here can be changed later on.

3. Press Add Application. The following screen will confirm creation of your LinkedIn application.

newlinkedinapp

4. As with Facebook, it is important that you take note of the API Key and Secret Key values that are displayed. We will need to use these values in our code (note that you are also provided with OAuth User Token and OAuth User Secret values; these are not required for this setup). Press Done. We will return to our application setup when we have identified the SharePoint 2010 web application with which we want to integrate LinkedIn.

Customizing the STS

First, add the oAuthBase2 and oAuthLinkedIn classes from Fatih YASAR to the App_Code folder of your STS project. You shouldn’t need to make any changes to the two .cs files, but you will notice that the oAuthLinkedIn class expects to find your API Key and Secret Key values in the of your STS’s Web.config, so add them there:

apiKeys

While we are here, it’s worth noting that the STS is configured by default to use the certificate with CN=STSTestCert to sign the SAML tokens it generates containing the claims we configure it to send. This certificate is installed as part of the Windows Identity Framework SDK. We will need to export this certificate so that we can configure SharePoint 2010 to use our custom STS as a Trusted Identity Provider and add this certificate to its trusted certificate store. I was able to find and export this certificate by loading the Certificates (Local Computer) snap-in and navigating to Trusted People > Certificates:

ststestcert

It goes without saying that you would not want to use this certificate in a production environment. Remember to update the SigningCertificateName in your STS’s Web.config to match the name of your production certificate.

Back to the STS code…I tied everything together in Login.aspx.cs. When the login page first loads (with no oauth_token value present in the query string), the user is redirected to the appropriate authorization link (hosted by LinkedIn). Once the user has an access token from LinkedIn, we are able to populate a series of claims for that user. Much like Travis did with Facebook, I make a call to the LinkedIn API to get profile information associated with the current user and define a series of output claims based on this information. The claims I have defined are:

  • Name – concatenated first and last name
  • Webpage – the LinkedIn user’s profile URL
  • NameIdentifier – the LinkedIn user’s profile ID (parsed from the profile URL)
  • GivenName and Surname (just because I could)

Below is the code that I used. In a production environment, you will want to include better error handling and more robust XML parsing!

public partial class Login : System.Web.UI.Page
{
    private oAuthLinkedIn liAuth = new oAuthLinkedIn();
    private string profileId = string.Empty;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.QueryString["oauth_token"] == null)
        {
            // If "oauth_token" does not appear in the query string, the user has not been authenticated
            // This call sets the Token and TokenSecret values
            string authLink = liAuth.AuthorizationLinkGet();
            Application["requestToken"] = liAuth.Token;
            Application["requestTokenSecret"] = liAuth.TokenSecret;
            Response.Redirect(authLink);
        }
        else
        {
            // User has been authenticated
            // Use the request token to get the access token
            liAuth.Token = (string)Application["requestToken"];
            liAuth.TokenSecret = (string)Application["requestTokenSecret"];
            liAuth.Verifier = Request.QueryString["oauth_verifier"];
            liAuth.AccessTokenGet(Request.QueryString["oauth_token"]);

            if (liAuth.Token.Length > 0)
            {
                // Make a LinkedIn API call to get claim information
                Dictionary claims = GetClaims();
                HttpContext.Current.Session.Add("LinkedInClaims", claims);
                // Set FedAuth cookie (without this line, SharePoint will not consider the user logged in)
                FormsAuthentication.SetAuthCookie(profileId, false);
                // Pass along the query string - STS default.aspx will redirect to SharePoint
                Response.Redirect("default.aspx?" + Request.QueryString.ToString());
            }
        }
    }

    private Dictionary GetClaims()
    {
        Dictionary claims = new Dictionary();
        string response = liAuth.APIWebRequest("GET", "https://api.linkedin.com/v1/people/~", null);

        if (!string.IsNullOrEmpty(response))
        {
            string firstName = string.Empty;
            string lastName = string.Empty;
            string headline = string.Empty;
            string url = string.Empty;

            // Parse values from response XML
            using (XmlReader reader = XmlReader.Create(new StringReader(response)))
            {
                reader.ReadToFollowing("first-name");
                firstName = reader.ReadElementContentAsString();
                reader.ReadToFollowing("last-name");
                lastName = reader.ReadElementContentAsString();
                reader.ReadToFollowing("headline");
                headline = reader.ReadElementContentAsString();
                reader.ReadToFollowing("url");
                url = reader.ReadElementContentAsString();
                profileId = GetProfileId(url);
            }

            claims.Add(System.IdentityModel.Claims.ClaimTypes.Name, string.Format("{0} {1}", firstName, lastName));
            claims.Add(System.IdentityModel.Claims.ClaimTypes.Webpage, url);
            claims.Add(System.IdentityModel.Claims.ClaimTypes.NameIdentifier, profileId);
            claims.Add(System.IdentityModel.Claims.ClaimTypes.GivenName, firstName);
            claims.Add(System.IdentityModel.Claims.ClaimTypes.Surname, lastName);
        }

        return claims;
    }

    private string GetProfileId(string url)
    {
        // Parse the profile ID from the profile URL
        string id = url.Substring(url.IndexOf("id=") + 3);
        return id.Substring(0, id.IndexOf('&'));
    }
}

Also, don’t forget to update the GetOutputClaimsIdentity() function in the CustomSecurityTokenService.cs class to make use of the output claims that are stored in a Session variable:

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["LinkedInClaims"] as Dictionary;
    if (oAuth20Claims != null)
    {
        foreach (var claim in oAuth20Claims)
        {
            outputIdentity.Claims.Add(new Claim(claim.Key, claim.Value));
        }
    }

    return outputIdentity;
}

Deploying the Custom STS and Updating the OAuth Redirect URL for the LinkedIn Application

Following the steps outlined in Travis’ blog post will give you an ASP.NET 4.0 web site that serves as your Security Token Service. You must deploy or publish this web site so that your SharePoint users will have access to it. For the purposes of this blog post, I have set up an IIS web site at http://sts.contoso.com that points to my custom STS. (Remember to set the .NET Framework version of your STS application pool to v4.0!)

Once I have deployed my STS, I need to return to my LinkedIn application setup and provide the URL to this STS as the OAuth Redirection URL, with a couple of important query string parameters appended:

1. Return to https://www.linkedin.com/secure/developer and click the name of your application.

2. In the OAuth User Agreement section near the bottom of the form, enter the URL to the Default.aspx page at the root of your STS site with the following query string:
?wa=wsignin1.0&wtrealm=http%3a%2f%2fintranet.contoso.com%2f_trust%2f

oauthagree

These parameters are defined in greater detail here. The wtrealm parameter should be the URL-encoded value of your SharePoint web application with /_trust/ (%2f_trust%2f) appended. As you can see, you also have the option of specifying a different URL if the user selects “Cancel” from the authorization dialog. Finally, you may also optionally specify a URL to an 80×80 logo image for your app (that must use SSL).

3. Press Save to update your application.

Configuring the Trusted Identity Provider for SharePoint 2010

We will configure our LinkedIn Trusted Identity Provider for SharePoint 2010 to map the following claim types that are included in the SAML tokens we receive from our custom STS:

  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier (LinkedIn profile ID)
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name (First and last name)
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/webpage (LinkedIn profile URL)

This is done by running the following PowerShell script:

$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("c:\STSTestCert.cer")
$map1 = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" -IncomingClaimTypeDisplayName "LinkedIn ID" -LocalClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"
$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"
$map3 = New-SPClaimTypeMapping -IncomingClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/webpage" -IncomingClaimTypeDisplayName "LinkedIn URL" -SameAsIncoming
$realm = "http://intranet.contoso.com/_trust"
$signinurl = "http://sts.contoso.com/"
New-SPTrustedIdentityTokenIssuer -Name "LinkedIn" -Description "LinkedIn custom STS" -Realm $realm -ImportTrustCertificate $cert -ClaimsMappings $map1,$map2,$map3 -SignInUrl $signinurl -IdentifierClaim $map1.InputClaimType
New-SPTrustedRootAuthority -Name "LinkedIn" -Certificate $cert

The script is fairly straightforward. A couple items to note:

  • The $cert defined in line 1 is created based on the exported STS signing certificate mentioned previously. In this case, I exported the certificate to my C:\ drive and reference it there.
  • Because certain claim type mappings are already used by SharePoint (such as name and nameidentifier), I had to define different LocalClaimType values for 2 of my 3 claim mappings.
  • I use the LinkedIn profile ID (and not the first and last name of the user) as the IdentifierClaim because only the profile ID is guaranteed to be unique (e.g., two different users could have the same first and last names). I will write some custom code to update the display name property of the SPUser objects associated with LinkedIn users so that they see their first and last name (instead of that 7-digit ID) at the top of the screen when they sign in to SharePoint.

Running this script will add a Trusted Identity Provider called LinkedIn to the list of Trusted Identity Providers that can be added to any web application through Central Administration:

After adding the new Trusted Identity Provider, it helps to define a User Permission policy for the web application that allows any users who authenticate using this Trusted Identity Provider to be authorized to have read access to the web application:

addusers

The Moment of Truth

Users should now be able to sign in to SharePoint with their LinkedIn accounts. Let’s give it a shot! Depending on the different authentication providers configured for a given web application, you may or may not see a sign-in page allowing you to choose which credentials to use to log in to SharePoint. We will choose LinkedIn:

A series of HTTP redirects will take place. If the user has previously logged in to LinkedIn and has a cookie, that user will not need to enter his/her credentials again. In this screen, the user is agreeing to allow the LinkedIn application to have access to his or her account information (which consists of name, profile headline, and profile URL):

signin2

If the user has not previously logged in to LinkedIn and/or does not have a cookie, the following screen will appear:

signin1

Users always retain the ability to revoke your application’s permission at any time. Press Allow access and some more HTTP redirects will take place that should eventually land the user back in SharePoint. I have used Travis Nielsen’s Claims Web Part on the team site where users log in with LinkedIn. Here you can see the claims mappings we configured earlier and how those claims are presented to SharePoint from our custom STS:

cvwp3

Because we are using the user’s LinkedIn profile ID as the nameidentifier claim, that value initially appears at the top right of the page when the user first signs in:

To improve the personalization experience for the end user, we can write the following code (in a web part, for instance) to update the DisplayName property of the SPUser to read the value from the givenname claim instead:

public class LinkedInNameChanger : WebPart
{
    string givenName = string.Empty;
    IClaimsIdentity claimsIdentity;
    Label lblOutput = new Label();
    Button btnFixUserName = new Button();

    protected override void OnLoad(EventArgs e)
    {
        // Read the incoming claims and assign values
        IClaimsPrincipal claimsPrincipal = Page.User as IClaimsPrincipal;
        claimsIdentity = (IClaimsIdentity)claimsPrincipal.Identity;

        foreach (Claim c in claimsIdentity.Claims)
        {
            if (c.ClaimType.EndsWith("givenname"))
            {
                givenName = c.Value;
                break;
            }
        }
    }

    protected override void CreateChildControls()
    {
        btnFixUserName.Text = "Fix Display Name";
        btnFixUserName.Click += new EventHandler(btnFixUserName_Click);
        this.Controls.Add(btnFixUserName);
        this.Controls.Add(lblOutput);
    }

    void btnFixUserName_Click(object sender, EventArgs e)
    {
        try
        {
            Guid siteId = SPContext.Current.Site.ID;
            Guid webId = SPContext.Current.Web.ID;
            SPUser currentUser = SPContext.Current.Web.CurrentUser;
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite(siteId))
                {
                    using (SPWeb web = site.OpenWeb(webId))
                    {
                        web.AllowUnsafeUpdates = true;
                        SPUser user = web.AllUsers[currentUser.LoginName];
                        user.Name = givenName;
                        user.Update();
                        lblOutput.Text = "
Display name successfully updated.
";
                    }
                }
            });
        }
        catch (Exception ex)
        {
            lblOutput.Text = "
Exception: " + ex.Message + "
";
        }
    }
}

There now, that’s better!