
I've been doing a lot of work with ADFS and SharePoint lately and I have to say, it is really cool! Like many developers I was hesitant to jump on the claims bandwagon when SharePoint 2010 came around but now that I have I don't intend on looking back.
That being said, it still feels like the implementation is in its infancy - at least in SharePoint 2010. There are some elements that don't seem to work right and some other features in SharePoint that just don't support a claims environment.
Of those not supported, my favorites are SSRS Report Deployments and the User Profile Service Profile Synchronization. SSRS reports work just fine, but you can't deploy your reports from visual studio in an SSO Claims application because SSRS is not capable of that. And profile sync in the UPS only works for Active Directory profiles that exist in the local Active Directory, so if you are federating users don't expect to be able to import or sync profiles (even with a BCS) connection without sitting down and writing your own ground-up profile synchronization engine. Frustrating shortcomings but not deal breakers, and I hold out hope that future implementations will overcome this.
Anyway, back to my original point. The biggest complaint I got from my customers is the Sign Out in SharePoint doesn't actually sign them out. It does sign them out of SharePoint, which means it blows up the cookie that SP uses to mark your authenticated session, but your identity is trusted from an external provider - which means as soon as you make another request to SharePoint it is going to reenter the auth cycle and get a new token from your Identity Provider (who still has an authenticated session for you). So in effect you never really signed out. The issue here is in an SSO environment when an application that trusts external identities receives a sign out request it needs to not only sign the user out of its local system but it also needs to tell the IDP that the user wants out. SharePoint doesn't do this for some reason and so we have to force it.
The core issue is SharePoint needs to first tell its Identity Provider that the user wants to sign out, the Identity Provider will sign the user out and then in turn send a wscleanout message to SharePoint, which signs the user out of SharePoint and completes the single sign out process. If you search around there are some solutions out there ranging from the very complicated writing of your own modules to override SharePoint functionality and rewrite sign out requests, to the more direct and less flexible user control hack. They each have their pros and cons but in my opinion the user control hack is the easiest and most often appropriate.
The idea is we are going to tell SharePoint to send the user request to the IDP when they ask for a sign out, rather than to the default SharePoint sing out page.
If you look in C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\CONTROLTEMPLATES there is a user control named Welcome.ascx. This is the control that is used for the user drop down menu in the upper right of your SharePoint site. Search through there and find the element that looks like this:
<SharePoint:MenuItemTemplate runat="server"
id="ID_Logout"
Text="<%$Resources:wss,personalactions_logout%>"
Description="<%$Resources:wss,personalactions_logoutdescription%>"
MenuGroupId="200"
Sequence="300"
UseShortId="true" />
This is the Sign Out menu item and it is what we are going to change. The change is simple: just add an attribute called ClientOnClickNavigateUrl and set it to the URI of your IDP's sign out page and pass the wssignout1.0 parameter. If you are using ADFS this will be like https://devadfs/adfs/ls/?wa=wsignout1.0 (where devadfs is of course your own ADFS server.)
The trick is you have to give the control a new ID, and I'm guessing this is because something else comes along and assigns that ClientOnClickNavigateUrl value when the page renders, so we have to change the ID so it can't find it (I know, it's a hack). The easiest thing to do is append a number to the ID field, so let's call it ID_Logout1.
When you are all done your new control should look something like this:
<SharePoint:MenuItemTemplate runat="server"
id="ID_Logout1"
Text="<%$Resources:wss,personalactions_logout%>"
Description="<%$Resources:wss,personalactions_logoutdescription%>"
MenuGroupId="200"
Sequence="300"
UseShortId="true"
ClientOnClickNavigateUrl="https://devadfs/adfs/ls/?wa=wsignout1.0" />
Once you have this in place you may have to set a SAML Logout endpoint in ADFS that points to https://[sharepointsite]/_trust/default.aspx?wa=wsignoutcleanup1.0. This is the normal trust page and it can receive a wssignoutcleanup1.0 parameter to sign the user out of SharePoint.
With these elements in place you should now be directed to your ADFS sign out page when you click the sign out link in SharePoint and you will be signed out of both your IDP and SharePoint.
One of the most exciting things about using ADFS in your application environment is the ability to have multiple disparate applications all trust a single authentication source - thus providing a true single-sign-on experience for your users. SharePoint 2010 and greater has the ability to trust these identities now as well, which makes integrating SharePoint into a production environment even more flexible and capable.
Since Infotekka is a development shop and we work extensively with SharePoint, we naturally have several SharePoint web applications that are hosted on a single farm. In order to help keep things consistent with our authentication and identities over all of these applications we have utilized ADFS. This works brilliantly however we hit a snag when the time came to add a second web application onto our existing farm.
To get off the ground we followed this great walk-through blog from Steve Peschka that you have likely already seen; using email address as our identifier: Configuring SharePoint 2010 and ADFS v2 End to End.
Our ultimate goal was to have two SharePoint web applications be able to share a single user profile service application and share an identity provider. Specifically, we wanted the profiles for all users to be consistent over both applications - which is exactly what the user profile service is meant to do.
Consider these conditions:
Application A (App-A): https://sp.somedomain.tld
Application B (App-B): https://sp.someotherdomain.tld
Both applications are hosted on the same SharePoint WFE and are in the same farm. They share a single User Profile Service application.
At first it seemed obvious that we could simply check the box next to the same Trusted Identity Provider in App-B as we had in the App-A. However we found very quickly that it isn't that easy. ADFS can only use a single WS-Federation Passive Endpoint per relying party, and since our URL was different in App-B, ADFS attempted to route its response back to App-A when we signed in to App-B. So clearly this won't work. We found some interesting code based solutions but they seemed over-complicated for what should be a reasonably easy solution. Ultimately we determined that we had to create a second Relying Party in ADFS for the second SharePoint application. In retrospect this makes perfect sense and helps keep things neat and manageable in ADFS. We created it (like the blog above guides us to) with two Relying party identifiers:
1. https://sp.someotherdomain.tld/_trust/
2. urn:someotherdomain:sharepoint
Now that we had added a second RP in ADFS we still needed to figure out how to get both applications to use the same user identities. Remember, user names include the claims identifier perfix tags, such as "i:05.t|adfs provider|dude@email.tld", so we had to ensure that the entire user name was the same over both applications - not just the email address.
So the next idea was to simply create a new Trusted Identity Provider for App-B and have it point to the new RP in ADFS with essentially all of the same settings as the provider for App-A. Sadly that wouldn't work for our needs, because we would have to give the Trust Identity Provider a unique name which would mean the claims tag in our usernames would not line up and our profiles wouldn't sync-up properly. So that idea got canned.
Then we found this incredible guide on Claims based Identity and Access Control with a couple of chapters on SharePoint: Claims-Based Single Sign-On for Microsoft SharePoint 2010.
Reading through this guide we found the gem that we needed: in PowerShell, the TrustedIdentityTokenIssuer object can support multiple ProviderRealms!! What does this mean? It means we can configure a new realm for App-B and add it to the existing Trusted Identity Provider, and since the realm is keyed off of the uri that is requesting it, it sends the appropriate realm to ADFS which then triggers our new RP for App-B.
The PowerShell command to do this was as easy as this:
$ap = Get-SPTrustedIdentityTokenIssuer -Identity "adfs provider"
$uri = New-Object System.Uri("https://sp.someotherdomain.tld/")
$ap.ProviderRealms.Add($uri, "urn:someotherdomain:sharepoint")
$ap.Update()
After you execute these commands you can display the new values in your TrustedIdentityTokenIssuer by typing this into the same PowerShell window:
$ap
This will print out all of the values that are set in that provider and you can now see the new values that you entered above in the ProviderRealms property.
Now that the additional provider realm had been added it was as easy as we had thought before, we just used the same Trusted Identity Provider for the second SharePoint web application and we've met our goal! Now it is possible to sign in to either SharePoint application and then hop over to the other one and retain your sign-in (with a couple of quick redirects back to ADFS to confirm of course). Since both applications trust the same Identity Provider you do not have to sign in to each one, which is exactly what ADFS is supposed to provide. More exciting than any of that though is now the user names are exactly the same in both applications - which means profile sync data will update both applications and the "My Profile" link in the user menu will take you to the same profile from either application.
From here, adding additional applications with the same Trusted Identity Provider should be as easy as:
1. Add a new Relying Party in ADFS
2. Add a new provider realm to the Trusted identity Provider
3. Assign the existing Trusted Identity Provider to the new application
If you've ever made the mistake of editing a SharePoint master page file in SharePoint Designer and then taken the same file into Visual Studio, you've found that your once neat and manageable markup has been littered with garbage code in the form of new attributes for many of your elements. These attributes, named __designer:Preview and __designer:Values are an artifact that SharePoint Designer leaves in your document for its own nefarious purposes and they serve absolutely no value when the file is used in a SharePoint site. The only thing those attributes seem to accomplish is to make the file impossible to traverse from a code view.
To get rid of this irritating junk code I worked out a handy regular expression that you can use in Visual Studio's Find and Replace dialog (make sure that you have the Use Regular Expression option selected):
__designer\:[^=]*[=\"][^"]*["]([^"]|(\n|:Wh))*["]
This expression matches any of those attributes and their corresponding values and if you set your replace string to be nothing at all it will clear all of them out; restoring peace and balance to the universe and rendering your master page once again workable. Huzzah!
Looking for our SharePoint development site? Look no further.
Not looking for it? Maybe you should be. Infotekka specializes in Microsoft SharePoint 2010 architecture, installation, configuration, and most importantly customization. If you'd like to know more please send us a note from the contact page.
The time has come to retire this website and implement a new one. We're likely going to stick with the same look and feel but it's moving off of it's current LAMP environment and going into .NET, where we can take advantage of all the fun integration stuff we're always crowing to our clients about.
Stay tuned for updates!