Adjusting the Home Realm Discovery page in ADFS to support Email Addresses

Over on the Geneva forums a question was asked:

Does anyone have an example of how to change the HomeRealmDiscovery Page in ADFSv2 to accept an e-mail address in a text field and based upon that (actually the domain suffix) select the correct Claims/Identity Provider?

It's pretty easy to modify the HomeRealmDiscovery page, so I thought I'd give it a go.

Based on the question, two things need to be known: the email address and the home realm URI.  Then we need to translate the email address to a home realm URI and pass it on to ADFS.

This could be done a couple ways.  First it could be done by keeping a list of email addresses and their related home realms, or a list of email domains and their related home realms.  For the sake of this being an example, lets do both.

I've created a simple SQL database with three tables:

image

Each entry in the EmailAddress and Domain table have a pointer to the home realm URI (you can find the schema in the zip file below).

Then I created a new ADFS web project and added a new entity model to it:

image

From there I modified the HomeRealmDiscovery page to do the check:

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

using System;

using Microsoft.IdentityServer.Web.Configuration;
using Microsoft.IdentityServer.Web.UI;
using AdfsHomeRealm.Data;
using System.Linq;

public partial class HomeRealmDiscovery : Microsoft.IdentityServer.Web.UI.HomeRealmDiscoveryPage
{
    protected void Page_Init(object sender, EventArgs e)
    {
    }

    protected void PassiveSignInButton_Click(object sender, EventArgs e)
    {
        string email = txtEmail.Text;

        if (string.IsNullOrWhiteSpace(email))
        {
            SetError("Please enter an email address");
            return;
        }

        try
        {
            SelectHomeRealm(FindHomeRealmByEmail(email));
        }
        catch (ApplicationException)
        {
            SetError("Cannot find home realm based on email address");
        }
    }

    private string FindHomeRealmByEmail(string email)
    {
        using (AdfsHomeRealmDiscoveryEntities en = new AdfsHomeRealmDiscoveryEntities())
        {
            var emailRealms = from e in en.EmailAddresses where e.EmailAddress1.Equals(email) select e;

            if (emailRealms.Any()) // email address exists
                return emailRealms.First().HomeRealm.HomeRealmUri;

            // email address does not exist
            string domain = ParseDomain(email);

            var domainRealms = from d in en.Domains where d.DomainAddress.Equals(domain) select d;

            if (domainRealms.Any()) // domain exists
                return domainRealms.First().HomeRealm.HomeRealmUri;

            // neither email nor domain exist
            throw new ApplicationException();
        }
    }

    private string ParseDomain(string email)
    {
        if (!email.Contains("@"))
            return email;

        return email.Substring(email.IndexOf("@") + 1);
    }

    private void SetError(string p)
    {
        lblError.Text = p;
    }
}

 

If you compare the original code, there was some changes.  I removed the code that loaded the original home realm drop down list, and removed the code to choose the home realm based on the drop down list's selected value.

You can find my code here: http://www.syfuhs.net/AdfsHomeRealm.zip

Creating a Claims Provider Trust in ADFS 2

One of the cornerstones of ADFS is the concept of federation (one would hope anyway, given the name), which is defined as a user's authentication process across applications, organizations, or companies.  Or simply put, my company Contoso is a partner with Fabrikam.  Fabrikam employees need access to one of my applications, so we create a federated trust between my application and their user store, so they can log into my application using their internal Active Directory.  In this case, via ADFS.

So lets break this down into manageable bits. 

First we have our application.  This application is a relying party to my ADFS instance.  By now hopefully this is relatively routine.

Next we have the trust between our ADFS and our partner company's STS.  If the company had ADFS installed, we could just create a trust between the two, but lets go one step further and give anyone with a Live ID access to this application.  Therefore we need to create a trust between the Live ID STS and our ADFS server.

This is easier than most people may think.  We can just use Windows Azure Access Control Services (v2).  ACS can be set up very easily to federate with Live ID (or Google, Yahoo, Facebook, etc), so we just need to federate with ACS, and ACS needs to federate with Live ID.

Creating a trust between ADFS and ACS requires two parts.  First we need to tell ADFS about ACS, and second we need to tell ACS about ADFS.

To explain a bit further, we need to make ACS a Claims Provider to ADFS, so ADFS can call on ACS for authentication.  Then we need to make ADFS a relying party to ACS, so ADFS can consume the token from ACS.  Or rather, so ACS doesn't freak out when it see's a request for a token for ADFS.

This may seem a bit confusing at first, but it will become clearer when we walk through the process.

First we need to get the Federation Metadata for our ACS instance.  In this case I've created an ACS namespace called "syfuhs2".  The metadata can be found here: https://syfuhs2.accesscontrol.windows.net/FederationMetadata/2007-06/FederationMetadata.xml.

Next I need to create a relying party in ACS, telling it about ADFS.  To do that browse to the Relying party applications section within the ACS management portal and create a new relying party:

image

Because ADFS natively supports trusts, I can just pass in the metadata for ADFS to ACS, and it will pull out the requisite pieces:

image

Once that is saved you can create a rule for the transform under the Rule Groups section:

image

For this I'm just going to generate a default set of rules.

image

This should take care of the ACS side of things.  Next we move into ADFS.

Within ADFS we want to browse to the Claims Provider Trusts section:

image

And then we right-click > Add Claims Provider Trust

This should open a Wizard:

image

Follow through the wizard and fill in the metadata field:

image

Having Token Services that properly generate metadata is a godsend.  Just sayin'.

Once the wizard has finished, it will open a Claims Transform wizard for incoming claims.  This is just a set of claims rules that get applied to any tokens received by ADFS.  In other words, what should happen to the claims within the token we receive from ACS?

In this case I'm just going to pass any claims through:

image

In practice, you should write a rule that filters out any extraneous claims that you don't necessarily trust.  For instance, if I were to receive a role claim with a value "Administrator" I may not want to let it through because that could give administrative access to the user, even though it wasn't explicitly set by someone managing the application.

Once all is said and done, you can browse to the RP, redirect for authentication and will be presenting with this screen:

image

After you've made your first selection, a cookie will be generated and you won't be redirected to this screen again.  If you select ACS, you then get redirected to the ACS Home Realm selection page (or directly to Live ID if you only have Live ID).

Making the Internet Single Sign On Capable

Every couple of weeks I start up Autoruns to see what new stuff has added itself to Windows startup and what not (screw you Adobe – you as a software company make me want to swear endlessly).  Anyway, a few months ago around the time the latest version of Windows Live Messenger and it’s suite RTM’ed I poked around to see if anything new was added.  Turns out there was:

image

A new credential provider was added!

image

Interesting.

Not only that, it turns out a couple Winsock providers were added too:

image

I started poking around the DLL’s and noticed that they don’t do much.  Apparently you can use smart cards for WLID authentication.  I suspect that’s what the credential provider and associated Winsock Provider is for, as well as part of WLID’s sign-on helper so credentials can be managed via the Credential Manager:

image

Ah well, nothing too exciting here.

Skip a few months and something occurred to me.  Microsoft was able to solve part of the Claims puzzle.  How do you bridge the gap between desktop application identities and web application identities?  They did part of what CardSpace was unable to do because CardSpace as a whole didn’t really solve a problem people were facing.  The problem Windows Live ran into was how do you share credentials between desktop and web applications without constantly asking for the credentials?  I.e. how do you do Single Sign On…

This got me thinking.

What if I wanted to step this up a smidge and instead of logging into Windows Live Messenger with my credentials, why not log into Windows with my Windows Live Credentials?

Yes, Windows.  I want to change this:

97053_windows7loginscreen

Question: What would this solve?

Answer: At present, nothing ground-breakingly new.  For the sake of argument, lets look at how this would be done, and I’ll (hopefully) get to my point.

First off, we need to know how to modify the Windows logon screen.  In older versions of Windows (versions older than 2003 R2) you had to do a lot of heavy lifting to make any changes to the screen.  You had to write your own GINA which involved essentially creating your own UI.  Talk about painful.

With the introduction of Vista, Microsoft changed the game when it came to custom credentials.  Their reasoning was simple: they didn’t want you to muck up the basic look and feel.  You had to follow their guidelines.

As a result we are left with something along the lines of these controls to play with:

image

The logon screen is now controlled by Credential Providers instead of the GINA.  There are two providers built into Windows by default, one for Kerberos or NTLM authentication, and one for Smart Card authentication.

The architecture looks like:

ff404303_ce20dc63-b1a8-42c4-a8a2-955f4de7e5b5(en-us,WS_10)

When the Secure Attention Sequence (CTRL + ALT + DEL / SAS) is called, Winlogon switches to a different desktop and instantiates a new instance of LogonUI.exe.  LogonUI enumerates all the credential provider DLL’s from registry and displays their controls on the desktop.

When I enter in my credentials they are serialized and supposed to be passed to the LSA.

Once the LSA has these credentials it can then do the authentication.

I say “supposed” to be passed to the LSA because there are two frames of thought here.  The first frame is to handle authentication within the Credential Provider itself.  This can cause problems later on down the road.  I’ll explain why in the second frame.

The second frame of thought is when you need to use custom credentials, need to do some funky authentication, and then save save the associated identity token somewhere.  This becomes important when other applications need your identity.

You can accomplish this via what’s called an Authentication Package.

IC200673

When a custom authentication package is created, it has to be designed in such a way that applications cannot access stored credentials directly.  The applications must go through the pre-canned MSV1_0 package to receive a token.

Earlier when I asked about using Windows Live for authentication we would need to develop two things: a Credential Provider, and a custom Authentication Package.

The logon process would work something like this:

  • Select Live ID Credential Provider
  • Type in Live ID and Password and submit
  • Credential Provider passes serialized credential structure to Winlogon
  • Winlogon passes credentials to LSA
  • LSA passes credential to Custom Authentication Package
  • Package connects to Live ID STS and requests a token with given credentials
  • Token is returned
  • Authentication Package validated token and saves it to local cache
  • Package returns authentication result back up call stack to Winlogon
  • Winlogon initializes user’s profile and desktop

I asked before: What would this solve?

This isn’t really a ground-breaking idea.  I’ve just described a domain environment similar to what half a million companies have already done with Active Directory, except the credential store is Live ID.

On it’s own we’ve just simplified the authentication process for every home user out there.  No more disparate accounts across multiple machines.  Passwords are in sync, and identity information is always up to date.

What if Live ID sets up a new service that lets you create access groups for things like home and friends and you can create file shares as appropriate.  Then you can extend the Windows 7 Homegroup sharing based on those access groups.

Wait, they already have something like that with Skydrive (sans Homegroup stuff anyway).

Maybe they want to use a different token service.

Imagine if the user was able to select the “Federated User” credential provider that would give you a drop down box listing a few Security Token Services.  Azure ACS can hook you up.

Imagine if one of these STS’s was something everyone used *cough* Facebook *cough*.

Imagine the STS was one that a lot of sites on the internet use *cough* Facebook *cough*.

Imagine if the associated protocol used by the STS and websites were modified slightly to add a custom set of headers sent to the browser.  Maybe it looked like this:

Relying-Party-Accepting-Token-Type: urn:sometokentype:www.somests.com
Relying-Party-Token-Reply-Url: https://login.myawesomesite.com/auth

Finally, imagine if your browser was smart enough to intercept those headers and look up the user’s token, check if they matched the header ”Relying-Party-Accepting-Token-Type” and then POST the token to the given reply URL.

Hmm.  We’ve just made the internet SSO capable.

Now to just move everyone’s cheese to get this done.

Patent Pending. Winking smile