SharePoint Custom Claims Provider

Recently one of our customers needed a way to move incoming claims from SiteMinder/ADFS into the user properties of the person logging in. In our case, it was critical to have the e-mail claim be passed into the “Work E-mail” user property on a private SharePoint portal. While it is true that there exists built-in functionality in SharePoint 2010 that will map user properties from Active Directory to user properties and synchronize them, it will not work for the ADFS authentication claims. In our case the users we were concerned with were not the users on our customer’s active directory server, but on an external server. For this reason, any incoming claims, including the e-mail claim, were just simply ignored by SharePoint.

Microsoft advised us to develop a custom claims provider, and the task ultimately came down to me. This was entirely alien to me as I had never done anything like this before, but I managed to pull it together in about a week’s time. Given how much documentation I had to search through to achieve this seemingly simple goal, I would like to save someone out there the trouble by disclosing some of the techniques I used to achieve this.

Understanding Claims-Based Authentication

There’s a decent Wikipedia page about claim-based identity that can help you out more on this but essentially it distills down to a system similar to OAuth where you have an authentication provider, a server that you provide credentials to which does not have anything to do with your service aside from the fact that you’re using their user database. Your client authenticates through that server and the server will provide your web service with whatever claims are configured (claims in this case being properties about the authenticating user that you wish to know or validate against).

Intercepting the default SharePoint authentication behavior for claims

After authenticating, the user will be redirected, along with a SAML document that is passed along, to the SharePoint site’s /_trust/ URL which will verify the token and run any customized claims processing. This is our entry point for the custom claims provider.

There’s another Wikipedia article about SAML (Security Assertion Markup Language) which you may find useful. It shows you some of the basic structure and flow used.

Overview of writing a Custom Claims Provider

The process is ultimately quite simple, after you get it.

  1. Create an empty SharePoint project in Visual Studio
  2. Create a class that implements the abstract SPClaimProvider
  3. Implement a couple of methods and properties (the main ones you will need are FillClaimsForEntity, and the DisplayName property)
  4. Add a SharePoint “feature” into the project and set up its properties
  5. Compile and deploy the application either through visual studio itself or by running some PowerShell magic. In my case, I had to make use of both since VS2010 is not exactly the most stable piece of software.

That’s essentially it. Sounds simple enough? Let’s get started.

The Set-up

In Visual Studio, create a new project in your solution. It should be an “Empty Sharepoint Project”. Create a class that will implement SPClaimProvider. At this point I won’t bother explaining too much detail because you will basically need to create a skeleton of all the methods you are going to implement. Follow the examples shown in the “Additional References” section below. Here’s the most important things out of what you will need to know, read it carefully!

  • The method that will be called for you when the user is authenticating is FillClaimsForEntity. This is your entry point to work with the authentication system and where you will put most of your logic.
  • The method FillClaimsForEntity will only be called if you have the property SupportsEntityInformation set to true.
  • In most situations when you use custom claims providers, you’re going to want to take some external claims and then add them to SharePoint claims. This is done by inserting them into the ‘claims’ list parameter that is passed along to you in FillClaimsForEntity. However, in my case I needed to update user properties with the data stored in the e-mail claim. If you are trying to accomplish something similar, you will not need that parameter.
  • Make sure your feature is set as a farm feature and not a web feature
  • Most of the other methods you will see in custom claim providers such as FillClaimTypes and FillClaimValueTypes you can just leave blank as for the functionality I described they are not needed. These two methods in specific that I just mentioned are required if you’re passing along the claims into SharePoint via the claims parameter.
  • All the “Supports” properties like “SupportsSearch” should be false except for the one I already mentioned, SupportsEntityInformation which should be true.

The Code

For obvious reasons I am not going to disclose the exact code I used as it is confidential to our customers but I will provide helpful snippets and links to some of the places I got them from.

The bulk of your code is going to be in the FillClaimsForEntity method, so let’s start off by looking at the process for it. At a high level you’re going to want to extract the incoming claims from the SAML document, locate the user associated with those claims in SharePoint, verify if any updates need to occur, and then perform the updates on the user properties that need updating. For the first part of these tasks, there is a very, incredibly helpful post about it on TechNet. Essentially, it distills down to using System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString to get the string representation of the SAML document with all the claims in it, then just navigating through  the XML DOM using System.Xml. There’s a full code snippet in that link. Some customizations I made were changing the incomingClaims into a dictionary instead of a list since we were only concerned with one claim of each type (e.g. only one e-mail) and found using a dictionary less bulky for retrieving the claim afterwards.

For the next part, you will need to know the username of the authenticating user. For this, I found the following useful snippet:

string userLoginEncoded = SPUtility.FormatAccountName("i", entity.Value);
string userLogin = SPClaimProviderManager.Local.DecodeClaim(userLoginEncoded).Value;

You may not need both for your purposes. In any case, userLoginEncoded represents the “i:…” format of username where as userLogin represents simply the username portion of it. To locate and update the user in Sharepoint we use the following code. Note that users don’t always have enough permissions to update their own properties, so you may need to run the code with elevated privileges like I have.

using (SPSite _site = new SPSite(*YOUR_SITE_URL_HERE*))
{
	using (SPWeb _web = _site.OpenWeb())
	{
		SPSecurity.RunWithElevatedPrivileges(delegate()
		{
			SPUser _myUser = _web.AllUsers[userLoginEncoded];
			if (String.Compare(_myUser.Email.ToLower(), incomingClaims[IncomingEmailClaimType].claimValue.ToLower()) != 0)
			{
				_myUser.Email = incomingClaims[IncomingEmailClaimType].claimValue;
				_myUser.Update();
			}
		});
	}
}

The rest of my methods are empty. I implement the Name property as per the name I wish to give my claim provider, and set SupportsEntityInformation to true, and the other Supports properties to false. Note that for the *YOUR_SITE_URL_HERE* you will need to provide the actual site URL and not just the host. Even though your SharePoint site may be rooted at http://whatever/, you will need to input http://whatever/sites/mysite/ instead here. To confirm that you have the right URL, you can use PowerShell and run Get-SPUser on the site you are implementing this for and see if the relevant users are there or not (e.g. if I’m trying to login as user1 and user1 does not exist in the list of users when I do Get-SPUser on http://foo/, then my code to find the user profile will also not work).

Make sure you add your Feature’s EventReceiver and update the properties in it (the instructions for this are outlined in MSDN).

Useful Tips for Debugging

  • Use Debug->Attach To Process and attach to all “w3wp.exe” processes (IIS app pool); this way you can make use of System.Diagnostics.Debug.WriteLine() in your code and see the output in Visual Studio.
  • When you run the “Deploy” command on your project, one of the steps VS will take is to recycle the application pool for your SharePoint website. However, claims and authentication are not run in the application pool of the SharePoint site but in a separate app pool called SecurityTokenApplication. In order to make sure your code is running up-to-date in SharePoint you will need to recycle that app pool as well manually.
  • When “Deploy”ing your application from VS you may encounter errors that the application pool could not be restarted because it couldn’t find the site. This may happen during the “Recycle Application Pool” step of deployment (in which case, try running Remove-SPClaimProvider in powershell before deploying) or during “activating features” in which case you don’t even need that step because farm features are automatically activated (so you can just change your deploy mode to “without activation” in that case).
  • You may want to increase the timeout period in IIS because while debugging you will probably hit the timeout often.

Useful tips for Deploying

In the SharePoint powershell, you can use Get-SPWebApplication to get the list of SharePoint applications. Locate the display name of the one you are developing for, and note the name. Then, execute Get-SPClaimProvider and find the claim provider you just wrote in the list (note that this will require doing Add-SPSolution (on the *.wsp project that will be in your project folder under /bin/*/*.wsp) and Add-SPClaimProvider beforehand). Given these two, you will then need to do something like the following to add your claim provider to the site you are working with:

$WebApplication = Get-SPWebApplication *YOUR_APPLICATION_NAME*
$IisSettings = $WebApplication.GetIisSettingsWithFallback("Internet")
$providers = $IisSettings.ClaimsProviders
$providers += *REFERENCE_TO_YOUR_PROVIDER_FROM_Get-SPClaimProvider*
Set-SPWebApplication -Identity $WebApplication -Zone "Default" -AdditionalClaimProvider $providers
Set-SPWebApplication -Identity $WebApplication -Zone "Internet" -AdditionalClaimProvider $providers

Additional References

7 Comments

Nadeem says:

Great blog post — thanks for sharing. It’s definitely an interesting first experience with SharePoint.

I think this line:
SPUser _myUser = _web.AllUsers[userLoginEncoded];
should probably be replaced by:
SPUser _myUser = _web.EnsureUser(userLoginEncoded);
in case the user never accessed the site before.

Also, some null-checking on _myUser.Email and incomingClaims[IncomingEmailClaimType] should probably be done.

Finally, each site collection (e.g. /sites/site1 and /sites/site2) maintains separate user information lists. The code would simply update Work E-mail in each site collection independently, so you’re good there.

You could run into issues if a user has never logged on to a SharePoint site collection before but he or she becomes the target of an approval workflow in that site collection. Their Work E-mail won’t be set until they access the site collection at least once, so they won’t get e-mails from the workflow. There isn’t any particularly elegant solution for this (not that I can think of), but I suppose you could create an event receiver and bind it to every site collection’s hidden User Information List to handle setting the Work E-mail field as soon as someone is referenced in the site collection. But this assumes that you have a way to look up an email address given a user id.

* Petro says:

Thanks Nadeem, good points. I actually just omitted to include the null checking in this post for the sake of keeping it simple, but it is there. Definitely will have a look regarding the other points though!

Edward says:

Petro, thanks for putting together this very enlightening blog. I have one question though. I am going to do very similar thing. But as per whatever I have read so far, I understand if you are using SAML2.0, you should be able to map incoming properties from ADFS claim with user profile properties and it should do the magic. Is it not true or you used SAML1.0?

* Petro says:

Hi Edward! I am not sure about the differences between SAML 2.0 and 1.0 and how they would affect this, but in our case we had to do it this way (claims provider) because user profile properties were not an option. I know for sure we tried to do this using user profile properties but something to do with ADFS and the configuration of the AD/SP servers kept us from being able to do that successfully (I don’t remember the specifics as this was a while ago). You should definitely give it a try and if you can manage to make it work using this option it would undoubtedly be the simpler and more elegant solution but it was not a possibility for our project. It should not take long to try it out and see if it works. Please let me know how it goes! Cheers.

Edward says:

Thanks a bunch for your response. I went for exactly the same solution that you have described in your post here. I am able to populate User Profile (or create new one if it does not exist) and get email address and display name from incoming token. My toke is coming from Google Account and I am using Azure ACS to federate. And as you suggested, I have not implemented other functions of Claim provider as I do not need them. I give All Users read access. Everything works great, except search. For google accounts, which has read permission and can see the docs, search result brings NOTHING. And for AD account, things it just works fine.

Any clue why? Thanks again for your great blog! As you know, there is not much documentation on these things.

[…] and blog post but all of them talks about custom providers like SqlMembershipProviders, Claims, and lot of stuff that I find not useful for this. Can anyone point me in the right […]

David Smith says:

I can’t seem to get the following code to execute inside FillClaimsForEntity.

I get the following Error:

“Error occurred in deployment step ‘Recycle IIS Application Pool’: Cannot connect to the SharePoint site: http://esp6436dt:25981/. Make sure that this is a valid URL and the SharePoint site is running on the local computer.”

SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite(context.AbsoluteUri.Substring(0, context.AbsoluteUri.IndexOf(“1”))))
{
SPWeb rootWeb = site.RootWeb;
SPListItemCollection userGroupItems = rootWeb.Lists[“Users List”].Items;
foreach (SPListItem item in userGroupItems)
{
if (item[“Title”].ToString() == currentUser)
{
usersGroups.Add(item[“Group”].ToString());
}
}
}
});

Leave a Reply

Your email address will not be published.