Home Realm Discovery In WIF And ADFS 2.0 By Query String

Update 2011-09-29: For a recent project at work I actually had to look at my own blog post to see how I solved this and it turned out to be wrong (I guess I didn’t use the RTW at the time, because I do remember it was working back then).

You don’t actually need any of the stuff in the HomeRealmDiscovery class, so just delete whatever is in there (just leave the class definition). For your aspx file, the only thing you need is the div with your message, and that should be all.

If you’re using asp.net in your RP, the event in global.asax.cs don’t actually have to be hooked up in Application_Start either. Just leave the RedirectingTo… method in there and asp.net will pick that up based on module convention (since the WSFederationAuthenticationModule is registered in your application, by convention asp.net looks in global.asax for WSFederationAuthenticationModule_[eventName]).

When working with passive federation you quickly run into Home Realm Discovery (HRD) (I couldn’t help notice that the short name became HRD which reads HARD :-) ). Anyways, HRD is about which identity provider (IP-STS) should authenticate the user and how to properly redirect the user to their home IP-STS. One example where HRD comes into play is when an organization have multiple partners that authenticate using their own internal STS. An example of that is illustrated in Figure1 below:

image
Figure 1

The Default Behavior
Using ADFS 2.0 as RP-STS you will by default be presented with this screen when you have trusts to one or more IP-STS’s:

image
Figure 2

Home Realm Query String
You probably do not want to reveal all of your federated partners like this. A better solution is to add a query string to the application URL where you specify the home realm, like this:

https://someWebApp/?whr=[Home Realm URI)

If the home realm (IP-STS) is ADFS 2.0, and using the URLs from Figure1 above, the URL would be:

https://rp.mydomain.com/?whr=https://ip-sts.partner1domain.com/adfs/services/trust

Let WIF Know About WHR
In order for your RP-STS to receive the whr parameter and automatically redirect the user to his home realm, you need to plug into the WIF pipeline of your application. You can do this very easily by editing your Global.asax.cs and listen to the RedirectingToIdentityProvider event like this:

using Microsoft.IdentityModel.Web;
...
protected void Application_Start(object sender, EventArgs e)
{
    FederatedAuthentication.WSFederationAuthenticationModule.RedirectingToIdentityProvider += new EventHandler(WSFederationAuthenticationModule_RedirectingToIdentityProvider);
}

void WSFederationAuthenticationModule_RedirectingToIdentityProvider(object sender, RedirectingToIdentityProviderEventArgs e)
{
    e.SignInRequestMessage.HomeRealm = Request["whr"];
}

Remove The Manual Home Realm Selection From ADFS

The next thing you probably want to do is prevent the RP-STS of displaying its home realm selection page. I didn’t find clear guidance of how to do this, but I found a solution that works. In the ADFS web folder of the RP-STS (C:\inetpub\adfs\ls\) you can edit the HomeRealmDiscovery.aspx and HomeRealmDiscovery.aspx.cs to display a message to the user and remove the dropdown for selecting home realm.

In HomeRealmDiscovery.aspx I removed the <div class="GroupXXLargeMargin"> section and changed the message “The site that you are accessing…” to something more informative. In the HomeRealmDiscovery.aspx.cs page I commented out everything related to the PassiveIdentityProvidersDropDownList control.

<%@ Page Language="C#" MasterPageFile="~/MasterPages/MasterPage.master" AutoEventWireup="true"
CodeFile="HomeRealmDiscovery.aspx.cs"
ValidateRequest="false"
Inherits="HomeRealmDiscovery" Title="Sign In"%>
<%@ Register TagPrefix="adfs" Namespace="Microsoft.IdentityServer.Web.UI" assembly="Microsoft.IdentityServer" %>
<%@ OutputCache Location="None" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
    <script type="text/javascript" src="FederationPassiveJScript.js">script>
        <div class="GroupXLargeMargin">
            The site that you are accessing requires Home Realm Discovery to sign in. Please contact your system administrator for further details.
        div>
        <asp:Panel ID="CardSignInPanel" Visible="False" runat="server" CssClass="GroupXXLargeMargin">Or, <a href="#" onclick="InfocardLink_onclick( document.infoCardObject );">sign
        ina> with an Information Card.
            <adfs:InformationCardControl ID="InformationCard" runat="server">adfs:InformationCardControl>
            <script>
                AddOnload( LoadCardPanel );
            script>
        asp:Panel>
asp:Content>


HomeRealmDiscovery.aspx

//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------

using System;

using Microsoft.IdentityServer.Web.Configuration;
using Microsoft.IdentityServer.Web.UI;

///

/// This page enables home realm discovery if this STS is configured to trust multiple claims providers.
///
/// If the persistIdentityProviderInformation setting is enabled and the user has previously
/// selected a claims provider, that claims provider will be used automatically.
/// 


public partial class HomeRealmDiscovery : Microsoft.IdentityServer.Web.UI.HomeRealmDiscoveryPage
{
    protected void Page_Init( object sender, EventArgs e )
    {
        //PassiveIdentityProvidersDropDownList.DataSource = base.ClaimsProviders;
        //PassiveIdentityProvidersDropDownList.DataBind();

        if( IsIssuedTokenViaSelectorEnabled() )
        {
            InformationCard.TokenSubmitted += TokenSubmitted;
            CardSignInPanel.Visible = true;
        }
    }

    private bool IsIssuedTokenViaSelectorEnabled()
    {
        foreach( AuthenticationTypeElement authenticationType in FederationPassiveConfigurationSection.Current.AuthenticationTypes )
        {
            if( authenticationType.Name == "IssuedTokenViaSelector" )
            {
                return true;
            }
        }

        return false;
    }

    protected void TokenSubmitted( object sender, InformationCardControl.TokenSubmittedEventArgs e )
    {
        SignIn( e.Token );
    }

    //protected void PassiveSignInButton_Click( object sender, EventArgs e )
    //{
    //    SelectHomeRealm( PassiveIdentityProvidersDropDownList.SelectedItem.Value );
    //}
}

HomeRealmDiscovery.aspx.cs

You then get a page like this if no whr is provided:

image  

Anything I can do about that URL?

If you don’t want your users to relate to the rather cryptic URL needed to support HRD there are several ways to make it a bit more user friendly. I will give a few suggestions here, but there are probably other (and maybe better) solutions than I can come up with right now.

  • Provide a shortcut on your users desktop or similar and have them use this to access the application
  • Detect by IP range and redirect to proper IP-STS
  • Provide a nice URL redirect either from the Relying Party (application) or IIS. E.g. https://someWebApp/YourCompany/ redirects to https://rp.mydomain.com/?whr=https://sts.yourCompany.lan/adfs/services/trust

One thing to note if you have the default settings in the web.config file for your RP-STS, specifically the set to true, is when a user have been redirected correctly to his/her home realm the RP-STS will issue a cookie to the user which contains the user’s home realm. If the user at a later time access the application from its root URL, he will be automatically redirected to his home realm. This is however only true as long as the cookie has not expired or the user uses the same computer as he did when the cookie was issued. Because of this I prefer sticking to one of the solutions above and not rely on the users having this cookie.

2 Comments

  • Jeremy

    The RedirectingToIdentityProvider event handler needs to be hooked up in an override of HttpApplication.Init(), not in Application_Start. The HttpModule that the event is fired from has not necessarily been loaded when Application_Start fires (it seems to never be in Cassini).

    29 Jun
    Reply
  • maccarvind

    Thanks Jon,
    This post was really useful and solved most of my problems during integration. Except the below one.

    I have the below setup,
    - WIF RP which points to RP-STS
    - RP-STS (Hybrid model ADFS 2.0 with AD account store)
    - IP-STS (ADFS 2.0)

    From your example I can make my application directly point to IP-STS server for authentication, bypassing / supressing RP-STS’s HRD page using the ‘whr’ querystring.

    My requirement is to point to RP-STS account store using ‘whr’ querystring. (Without making any change to the ADFS 2.0 code).
    - How to find the identifier for RP-STS account store,
    - I have tried ‘urn:federation:self’ but it isn’t working.

    thanks in advance.

    4 Nov
    Reply