Active Directory Federation Services and YubiKeys

The conclusion to my previous post was that I’ll be showing how to implement YubiKeys along with Active Directory Federation Services. So, where do we start on this topic?…

It’s sort of a logic at play here that says that if you aren’t familiar with Active Directory Federation Services (from here on abbreviated as ADFS) a lot of this post will not make sense to you at first glance. So, if you are familiar with ADFS skip ahead – if not I’ll have a few paragraphs explaining why you might be interested in taking a look at ADFS.

Surely everyone has noticed that there are a lot of web sites where there’s two options for signing in; either using an account for that particular site or "use your Google/Facebook/Twitter account to sign-in". The basic concept is easy enough – you already have a user identity, so why would you need another one? Why can’t you re-use the existing one? If you have ever logged on to a domain-joined Windows computer you’ve experienced this already. There is a central user catalog called "Active Directory" that you sign in to, and after being verified there you can access your file shares, Exchange account, etc without needing to sign into each and every one of those services.

That is certainly a good reason for re-using the identity you already have, but there’s another one as well. A lot of programmers are doomed to repeat the failures of others due to their insistence of doing things from scratch. What are the odds that I will be able to code (on my first attempt) a secure login solution that is resistant against cross-site scripting, SQL injection, buffer overflows, and whatnot? (Hint: don’t go all in betting on my success.) For some reason Facebook doesn’t instill a lot of confidence in me when it comes to protecting their users, though they are probably still better at it than me, but at least Google and Windows Live give me the impression of having done a thing or two to proof their solutions.

And that is the premise of having these alternative login methods – let me focus on creating a good web site, and let "the big ones" create good login solutions. And let the end-users avoid having to create ten different accounts on different sites (a lot of them choosing the same password for all sites in the process).

These login solutions are often called federated or claims-based, but I’m not going to concern myself with the naming here. It’s a large and complicated area, and there are plenty of solutions available for solving different scenarios whether it’s on-site, clouded, hybrid, and so on.

Where does ADFS come into play here then? Well, using Facebook and Google is all nice and dandy, but it’s not like your average enterprise would create their user directory in any of those places. We’ve already got this nice thing I already mentioned called Active Directory. And that’s where ADFS comes in – it builds a federation service on top of your Active Directory. Previously this wasn’t an issue – if I wanted to login to Outlook Web Access I’d just type in my username and password and it’s all good. Because Exchange happens to be integrated with my domain. But what if I’m using Office 365 or another clouded Exchange where the server is not situated in my domain? You can maintain separate identities and not having your AD account in sync with your mail account. But let’s face it – that is a hassle.

Microsoft supports a couple of different scenarios for logging into Office 365, and ADFS happens to be one of them. When you sign in to your Office 365 webmail you are redirected to an ADFS server installed in your infrastructure, authenticated, and then redirected back to Office 365.

You can of course extend this to other services as well, and for me personally I started to play around with it when I deployed a service to Windows Azure. There’s things I like about deploying to Azure, both from the developer perspective (I don’t have to deploy any servers) and from the admin perspective (I don’t have to deploy any servers). But just because I deploy to Windows Azure doesn’t mean I don’t want proper access control.

Now, you could write plenty of articles on how to integrate Azure and Active Directory. (Microsoft unsurprisingly has done so already.) So for the sake of this article let’s just accept the fact that you have some solution running somewhere and you want to use ADFS as the tool for integrating with your Active Directory.

I’ll also assume that you have installed ADFS on a server in your domain already. Getting it to run in the very basic scenario is pretty straightforward anyways.

With the default configuration you’ll be using integrated authentication while on the LAN or basic when outside of the LAN. This will of course only allow a normal username & password combo leaving no room for an additional factor of authentication. So, the first thing we’ll do is to change the authentication scheme to use forms. It’s a matter of just editing a few lines in the web.config file:
(Default path C:\inetpub\adfs\ls\)

Change from:

 
<microsoft.identityserver.web>
   <localauthenticationtypes>
      <add page="auth/integrated/" name="Integrated" />
      <add page="FormsSignIn.aspx" name="Forms" />
      <add page="auth/sslclient/" name="TlsClient" />
      <add page="auth/basic/" name="Basic" />
   </localauthenticationtypes>

To:

 
<microsoft.identityserver.web>
   <localauthenticationtypes>
      <add page="FormsSignInOTP.aspx" name="Forms" />
   </localauthenticationtypes>

That should give you a decent default layout. It’s even localized for a number of languages by default, although there were a couple of typos in the Norwegian version I had to correct (not shown here).
image

And the Yubikey fits into this in what way you say? Well, the thing about the ADFS web site is that you can hack it to behave the way you want it to. Microsoft implemented it as a basic web site, (as opposed to a compiled web app), so you have both the HTML and C# files to modify as you seem fit.

Let’s add another text box first:
Copy FormsSignIn.aspx to FormsSignInOTP.aspx.
(This is just to make sure you have a working config just in case you mess up something.)
Append a new <asp:TextBox> below the existing PasswordTextBox; let’s call it an OTPTextBox Smile

 
        <tr>
            <td>
                <span class="Label"><asp:Label Text="<%$ Resources:CommonResources, PasswordLabel%>" runat="server" /></span>
            </td>
            <td>
                <asp:TextBox runat="server" ID="PasswordTextBox" TextMode="Password" ></asp:TextBox>            
            </td>
            <td>&nbsp;</td>
        </tr>
        <tr>
            <td>
                <span class="Label"><asp:Label Text="One Time Password:" runat="server" /></span>
            </td>
            <td>
                <asp:TextBox runat="server" ID="OTPTextBox" TextMode="Password" ></asp:TextBox>            
            </td>
            <td>&nbsp;</td>
        </tr>

And make sure you modify the header to point to a different codefile (that we will be creating in the next step):

 
<%@ Page Language="C#" MasterPageFile="~/MasterPages/MasterPage.master" AutoEventWireup="true"  ValidateRequest="false"
    CodeFile="FormsSignInOTP.aspx.cs" Inherits="FormsSignIn" Title="<%$ Resources:CommonResources, FormsSignInPageTitle%>"
    EnableViewState="false" runat="server"%>
<%@ OutputCache Location="None" %>

The result should look like this if you don’t style it any further:
image

Then we need to wire up some logic that acts on this text box:
Create a new file;FormsSignInOTP.aspx.cs (in the same directory as the other files).
Add the following into the new file:

using System;

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

using System.Net;
using System.IO;

/// <summary>
/// Attempts to authenticate the user via HTTP Forms Authentication.
/// </summary>
public partial class FormsSignIn : FormsLoginPage
{
    protected void Page_Load( object sender, EventArgs e )
    {
    }

    protected void HandleError( string message )
    {
        ErrorTextLabel.Visible = true;
        ErrorTextLabel.Text = Resources.CommonResources.IncorrectUsernameText;
    }

    protected void SubmitButton_Click( object sender, EventArgs e )
    {
        string OTP = OTPTextBox.Text;
        string authId = "xxxx";
        string server = "https://api.yubico.com/wsapi/verify";

        string url = String.Format("{0}?id={1}&otp={2}", server, authId, OTP);

        string result = "false";
        try
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            StreamReader reader = new StreamReader(response.GetResponseStream());
            string str = reader.ReadLine();
            while (str != null)
            {
                str = reader.ReadLine();
                if (str.StartsWith("status="))
                {
                    if (str.StartsWith("status=OK"))
                    {
                        result = "true";
                    }
                    break;
                }
            }
        }
        catch (WebException webEx)
        {
            HandleError (webEx.Message);
        }

	if (result == "false")
	{
	   HandleError ("Failed OTP");   
	   
	}

	if (result == "true")
	{
           try
           {
              SignIn( UsernameTextBox.Text, PasswordTextBox.Text );
           }
           catch ( AuthenticationFailedException ex )
           {
              HandleError( ex.Message );
           }
	}
    }
}

It’s not all that different from the standard form; we just added some code to talk to the Yubico servers. (You’ll need to go to https://upgrade.yubico.com/getapikey/ to acquire an authId to substitute in your code.) If you’re running your own YubiKey servers you’d have to change the server address accordingly.

I’m doing the login procedure in a specific sequence; I verify the OTP first, and only if the OTP is valid are the other credentials passed along to Active Directory for authentication.

There is one minor omission in this sample. I’m checking that your AD credentials are valid. I’m checking that the OTP generated by your YubiKey is valid. But I am not checking that the YubiKey belongs to your user account. So any YubiKey could be used really. This is on purpose for this exercise, but is probably not what you want for production. What you would need to do is include some code that checks the id of your YubiKey (the first 12 digits of the OTP).

This means you have to figure out how to deploy the YubiKeys to the end-users; do the users pair them up themselves on a web site? Do you register the id in a database before handing out keys? How do you handle users changing YubiKeys? Do you allow users to have more than one key?

It’s not much, but I hope this has inspired you to look further into two-factor authentication, especially for mobile devices Smile 

For further info I can recommend the following links:
http://www.yubico.com/developers-intro
http://code.google.com/p/yubico-dot-net-client (helped me out in writing this article)

One thought on “Active Directory Federation Services and YubiKeys”

Leave a Reply

Your email address will not be published. Required fields are marked *

*