ADFS & RelayState

Active Directory Federation Services (ADFS) has been around for some time now, and many organizations use it to provide single sign-on capabilities to Office 365 without giving it a second glance, but ADFS is really a generic identity provider that can work with other Security Assertion Markup Language (SAML) 2.0 endpoints. The incredibly over-simplified gist of SAML is that some identity provider (ADFS + Active Directory) authenticates a user, hands them a token, and the user takes that token to log in to other web applications such as Office 365, Salesforce, Workday, Jobvite, or any SAML 2.0 compliant service. The benefit here is organizations have a single point of authentication in ADFS, but can use those credentials to access multiple services that they don’t necessarily host or deploy.

Often times these services will email a user with a hyperlink embedded in the message that links to a specific page or “deep link” on the service. If a user clicks on that link, it should take them directly to the referenced opportunity or page. The issue with the initial release of ADFS was that this feature did not work. The users could click one of these links and get logged in to the service, but they would always end up on the home or main page – not the link they clicked on. The reason for this is that ADFS did not support the RelayState parameter, which actually contains that end state or desired URL after login occurs. Update Rollup 2 for ADFS adds this functionality and can be downloaded from here: http://support.microsoft.com/kb/2681584 .

However, this does not mean that RelayState will begin working automagically. There are some requirements about how the URL is formatted in order for ADFS to properly consume the RelayState and send the user to the link after authentication. Those guidelines are laid out here, and includes the fact that part of it must already be URL encoded: http://technet.microsoft.com/en-us/library/jj127245(v=ws.10).aspx

So the options are to have the service provider deliver deep links in the format ADFS requires (unlikely to happen), or modify the links to the correct format. Fortunately, we can use the IIS URL Rewrite Module to match and manipulate patterns based on regular expressions (your Lync voice administrators should be very familiar with this concept.) Install the URL Rewrite module on your ADFS servers from here: http://www.iis.net/downloads/microsoft/url-rewrite

Let’s work through an example that uses the Jobvite service. I apologize for the color barf throughout the rest of the post, but I hope it highlights how everything lines up. A deep link that a user receives in an email from Jobvite resembles the following format:

https://www.jobvite.com/m/default.aspx?3EGNofwx

When a user clicks that link, Jobvite knows the tenant is enabled for SSO, and provides a redirect URL to the browser where the user should authenticate. It’s the equivalent of saying “I don’t authenticate you, but this URL (ADFS) can. Come back when you have a token that says you’ve authenticated.” In Jobvite’s case, that URL includes an identifier for ADFS to match a relying party trust (found in the loginToRp parameter), and two RelayState parameters – RelayState and Target. The values in RelayState and Target are identical, so I can only assume Jobvite is hoping the SSO provider will consume one of these. Unfortunately, ADFS doesn’t like either! This is the URL a user is redirected to by Jobvite:

https://sso.confusedamused.com/adfs/ls/idpinitiatedsignon.aspx?loginToRp=http://jobvite.com/saml&RelayState=/m/default.aspx?3EGNofwx&target=/m/default.aspx?3EGNofwx

Now the user’s browser flips to the ADFS link and logs in. Afterwards, the user is dumped back in to the main Jobvite page because the RelayState parameter didn’t match ADFS’s expected format. There are no errors, and a user is now logged in to Jobvite, but they didn’t end up on the URL they clicked in the email. It’s more of an annoyance than a major issue, but it can be fixed. The format ADFS wants to see looks like this and the part in italics must be URL encoded.

https://sso.confusedamused.com/adfs/ls/idpinitiatedsignon.aspx?RelayState=RPID=[Relying Party ID]&RelayState=[Relay State]

Covering how regular expressions or the rewrite module works is outside the scope of this article, but to be quick:

1. We match a specific URL and pattern received by the IIS server.

2. We then “rewrite” or modify that URL before it is actually processed and delivered to the ADFS application.

Start by opening the URL Rewrite module and click “Add Rule(s).” Select “Blank rule” and click OK.

First we need to set a match URL. The URL is in this window is really just the FQDN part of the URL, so in this example it would be sso.confusedamused.com. I’ve simply entered .* as the match URL pattern to allow any FQDN that points to this server to be accepted. I could use DNS to reach this server via sso.confusedamused.com, adfs.confusedamused.com, yourmomgoestocollege.confusedamused.com, or anything else and it would still match.

image

Next, we need a pattern to match. Expand the Conditions section and press “Add". The condition input we’ll check is the {REQUEST_URI}, which refers to everything after the FQDN we just checked. The pattern we’ll use looks like this.

^(.*)/adfs/ls/idpinitiatedsignon.aspx\?loginToRp=(.*)&RelayState=(.*)&(.*)$

image

In English, the pattern means “Must start with”, “any string” (that’s the .* part), followed by the exact text “/adfs/ls/idpinitiatedsignon.aspx\?” (we have to escape the “?” with a “\”), followed by loginToRp=”, capture the next “any string” (because this one is in parentheses it means we’re capturing this data to re-use in the rewrite later), followed by the exact string “&RelayState=”, capture the next string, followed by the exact string “&”, capture the next string, and finally, “must end with”. Technically, we don’t need to capture that last string because it just contains the target=/m/default.aspx?3EGNofwx part, which we already know is identical to the RelayState parameter passed right before it. But you get the idea.

For reference, here is the URL again that matches the above pattern. The colors should line up.

https://sso.confusedamused.com/adfs/ls/idpinitiatedsignon.aspx?loginToRp=http://jobvite.com/saml&RelayState=/m/default.aspx?3EGNofwx&target=/m/default.aspx?3EGNofwx

In the Action section the type we’ll select is rewrite. Remember how we “captured” some parts of text in the pattern previously? Those line up to variables that we can now reuse and reference in the rewrite pattern in the format {C:[Variable Number]}. So we have {C:1} that matches the first string we captured, and {C:2} matches the second one, etc. {C:1} actually refers to the very beginning of our pattern (the first (.*)), so the text we’re interested in starts at {C:2} which is the Relying Party ID, and includes {C:3}, our RelayState link. 

Just to refresh, the end-state we need to get to is this, and we have to URL encode everything after the first RelayState=:

https://sso.confusedamused.com/adfs/ls/idpinitiatedsignon.aspx?RelayState=RPID=[Relying Party ID]&RelayState=[Relay State]  

The URL Rewrite module actually includes a function to URL Encode a section of text so the rewrite to use will look like this:

adfs/ls/idpinitiatedsignon.aspx?RelayState={UrlEncode:RPID={C:2}&RelayState={C:3}}

image 

After that, simply select “Stop processing of subsequent rules” and uncheck the box “Append query string.” The end result is that the original link returned by Jobvite that ADFS doesn’t recognize…

https://sso.confusedamused.com/adfs/ls/idpinitiatedsignon.aspx?loginToRp=http://jobvite.com/saml&RelayState=/m/default.aspx?3EGNofwx&target=/m/default.aspx?3EGNofwx 

… will be transformed into this URL before it hits ADFS. The user experience is now that a deep link works as expected. A user that is not logged in to Jobvite can click a URL from an email, be seamlessly authenticated via ADFS, and end up on the URL they clicked on.

https://sso.confusedamused.com/adfs/ls/idpinitiatedsignon.aspx?RelayState=RPID%3Dhttp%3A%2F%2Fjobvite.com%2Fsaml%26RelayState%3D%2Fm%2Fdefault.aspx%3F3EGNofwx

To recap – the process goes:

1. User clicks link in email: https://www.jobvite.com/m/default.aspx?3EGNofwx

2. Jobvite redirects user to this link: https://sso.confusedamused.com/adfs/ls/idpinitiatedsignon.aspx?loginToRp=http://jobvite.com/saml&RelayState=/m/default.aspx?3EGNofwx&target=/m/default.aspx?3EGNofwx

3. URL Rewrite module matches the redirect link pattern and modifies the link to the following before delivering to ADFS: https://sso.confusedamused.com/adfs/ls/idpinitiatedsignon.aspx?RelayState=RPID%3Dhttp%3A%2F%2Fjobvite.com%2Fsaml%26RelayState%3D%2Fm%2Fdefault.aspx%3F3EGNofwx

You can create as many rewrite rules as necessary in case different service providers each send unique formats with the RelayState parameter.

Communicator and Damaged Address Book Files

The past few days I spent wrestling an address book server issue in OCS and I wanted to share the solution. The quick version: make sure you have your server side and client side hotfix revisions match up.

If you want the whole story…the specific details of this case involved a Front-End server which had the OCS 2007 R2 April hotfixes, but the MOC clients had the July hotfixes applied. The issue first manifested itself with clients reporting ABS damaged:

Communicator cannot synchronize with the corporate address book because the corporate address book file appears to be damaged. Contact your system administrator with this information.

We resolved this by deleting the entire contents of the address book file share and forcing a resync of the address book. We also deleted GalContacts.db from a few user workstations, but later found the client error actually disappeared on its own without removing the file.

Things hummed along nicely for a week or so until a large amount of users (600+) were enabled for OCS one Friday evening. The following Monday previously enabled pilot users were reporting they still didn’t have a SIP URI in their address books for the mass-enabled users. The GalContacts.db file was also still showing a timestamp from the day the mass change occurred, indicating they had not downloaded an update yet.

We took a peek at the Front-End logs and it appeared to be generating address book files correctly. The odd thing was in looking at the IIS logs we actually saw quite a few 404 errors of MOC clients trying to request delta files that did not exist. Other users showed successful downloads of the latest delta files which should have included the changes, but they weren’t being applied to their local GalContacts.db for some reason. I also saw those same clients registering a success end up using the fallback logic and downloading older address book files even though they had the newer versions. Very, very strange. Any client we deleted GalContacts.db on would pull down the latest full address book with no issues. The clients looking for deltas that didn't exist we probably caused by deleting the address book files previously.

Side tip when looking at the IIS logs: Full files start with F-xxxx and delta files follow a D-xxxx-xxxx naming convention. Also, .lsabs files are used by MOC while .dabs files are used by Tanjay devices.

At that point I noticed the mismatch in server (April hotfixes) vs. client (July hotfixes) versions and suggested we get the latest fixes installed on all sides. While that suggestion made its way through change control procedures we opened a PSS case with Microsoft to hit the problem from another angle. The engineer we spoke with immediately blurted out that we needed to match the hotfix versions as soon as we described the behavior. It sounded to me like this was one he had heard before or was familiar with so while we didn’t have a second approach this definitely helped accelerate the change control ticket to an authorized state. After we fully patched the Front-End using the new ServerUpdateInstaller (a lifesaver), applied the back-end database hotfix, and installed the client October hotfix the address book went back to functioning properly. There were a couple of users that needed to delete the GalContacts.db before everything went back to normal, but most of them picked it up without intervention.

As for root cause, the KB 972403 article actually does reference applying both the MOC and server fix together, but the July server hotfix document doesn’t describe this behavior or even mention it. Personally, I think the underlying issue was having the 3.5.6907.37 hotfix on clients while the abserver.exe file was still at 3.5.6907.0. In any case, I learned a lot more about the ABS than I ever cared to, but it was great information that will surely help in the future.

OAB Never Downloads for Outlook 2007 Clients with Exchange 2007 on Server 2008

Update: This post gets a lot of traffic, but I want to be clear the first step here is no longer required. Simply perform the solution at the end of the post.

This one killed me today. Exchange 2007 SP1, with Rollup Update 6 on Server 2008. Everything working perfectly with one exception – the offline address book (OAB) never downloads from the file distribution point for Outlook 2007 clients. Works fine via public folders, but not web-based. No error, no timeout, no progress indicator, no login prompt, Outlook just looks like it’s endlessly trying to download the OAB. I double-checked all the URLs, flipped around SSL settings, but still couldn’t figure out why it wouldn’t download. I would have been happy to see an error so I had something to search on. There were actually 2 problems here that made the situation a real pain in the ass.

First – the same bug that affects Outlook Anywhere on Server 2008 apparently does a number on the OAB too. The solution is to turn off kernel-mode authentication in IIS. Run this command to fix that issue and you’re halfway there. I ran across some blog that mentioned Rollup Update 7 may include this change by default.


C:\Windows\system32\inetsrv\appcmd.exe set config /section:system.webServer/security/authentication/windowsAuthentication /useKernelMode:false

Second – I had enabled a redirect at the Default Web Site root to dump clients to the /owa folder gracefully using the Microsoft methodology at Technet. If you read the procedure you’ll notice setting the redirect at the root sets the same redirect on every single virtual directory. So, you need to go in to each virtual directory and undo the change you made for the root. This works fine, or appears to until your Outlook 2007 client tries to download the OAB and hangs forever.

I brightly plugged the URL to the OAB.XML file into IE and was greeted with a 500 – Internal Server Error message without an authentication prompt. That didn’t seem right. After some searching I realized the reason why Outlook hangs forever is that it tries to hit this URL, gets denied, uses some back-off logic, and tries again. I believe the back-off gets longer and longer each time it fails.

What happens is that when you disable that redirect for the OAB virtual directory IIS 7 generates a web.config file in the C:\Program Files\Microsoft\Exchange Server\ClientAccess\OAB folder. This seems logical, as it overrides the redirect at the root level, and is necessary. Unlike every other web.config that is generated in the other folders like Autodiscover and OWA, Authenticated Users do not have read access to the file. This is why Outlook and IE can’t even access the /OAB virtual directory.

The fix is pretty easy. Open the web.config in the OAB folder, and give Authenticated Users both the read and read and execute permissions. Run a iisreset /noforce on the CAS server to bounce IIS. Just for good measure, on the client side I wiped out the Outlook profile, and the contents of the %USERPROFILE%\Local Settings\Application Data\Microsoft\Outlook folder. Once I recreated the profile the OAB downloaded just fine. All in a day’s fun…