Home / My Disclaimer / Who am I? / Search... / Sign in

Multifactor Authentication with ADFS v2

by Steve Syfuhs / February 21, 2011 04:00 PM

More and more companies are requiring that their IT departments start introducing multifactor authentication into their systems as the threat landscape changes over time.

Multifactor authentication isn’t a new concept – it’s been around for quite a few years within the enterprise.  Microsoft has supported this for a decade in Windows.

However, it’s just now starting to become a popular solution in the consumer space.  Proof of this is PayPal introducing their hardware tokens a few years ago as well as Google introducing its support for two-factor authentication not too long ago.

I can’t really explain why it took this long to make the shift.  So, frankly, I’m not going to try. Smile

This post is simply about getting ADFS to support multifactor authentication.

The premise is simple.  We want two secrets from the principal.  Their password and a pin.  The password is secret; it’s what you use to log into Windows.  The pin can be any number of things.  It could be a one-time-password (OTP) that is generated via a cryptographically sound algorithm, or it could be a secret stored on a smart card.

For the sake of this article, we don’t actually care what it is.  Let’s assume it’s an OTP.

First things first.  ADFS works via Windows Authentication, which allows for the single sign-on experience.  We don’t actually want this because we need to be able to enter in the secondary credential.  Within the web.config file for the ADFS website, you have a section called localAuthenticationTypes.  By default Integrated is the authentication type of choice, which is Windows Auth.  Let’s remove it, and everything else but Forms.  This will tell ADFS to render the FormsSignin.aspx page when authentication is required.

<localAuthenticationTypes>
<!--  <add name="Integrated" page="auth/integrated/" /> -->
  <add name="Forms" page="FormsSignIn.aspx" />
<!--  <add name="TlsClient" page="auth/sslclient/" /> –>
<!--  <add name="Basic" page="auth/basic/" />-->
</localAuthenticationTypes>

When we go to log into ADFS, we are now presented with a new page:

image

There isn’t anything particularly exciting about this page on a code level.  The markup is a table with some textboxes.  The code is just a callout to a method in the base class that validates the credentials from the textboxes via Windows Authentication:

protected void SubmitButton_Click( object sender, EventArgs e )
{
    try
    {
        SignIn( UsernameTextBox.Text, PasswordTextBox.Text );
    }
    catch ( AuthenticationFailedException ex )
    {
        HandleError( ex.Message );
    }
}

In a two-factor authentication world, we now have half the requirements.  Now we need to collect the pin, so take the markup and add a new row, with a textbox.

image

Next we need to validate the second credential.  The validation is somewhat arbitrary because I haven’t defined what sort of credential it is, so we just need to mock something up:

protected void SubmitButton_Click( object sender, EventArgs e )
{
    try
    {
        string userName = UsernameTextBox.Text;
        string pin = PinTextBox.Text;
        
        if(!ValidatePin(userName, pin));
             throw new AuthenticationFailedException();
             
        SignIn(userName, PasswordTextBox.Text);
    }
    catch ( AuthenticationFailedException ex )
    {
        HandleError( ex.Message );
    }
}

Next you compile it, and deploy.  Don’t forget what i said about properly securing your ADFS deployment Smile.

How you handle the pin validation is dependent on the technology, but that is about all there is to it when it comes to a simple two-factor authentication mechanism for ADFS v2.

Comments (9) -

Milos Cekovic
Milos Cekovic Switzerland
11/24/2011 7:03:25 AM #

Hi Steve,
Do you have an example how I can do two factor authentication, if AD FS has to first provide the challange (which is based on the username) and then let the user enter the response. This means that the user first needs to enter username and password, then clicks on Sign In, then the ADFS retrieves the challenge for the entered username, then the user provides the response, and clicks on the Sign in, then the AD FS does challenge/response validation.
Cheers, Milos

Steve Syfuhs
Steve Syfuhs Canada
11/25/2011 6:09:13 PM #

Hi Milos,
In order to get the challenge/response working you would have to do override the SignIn method. Does the challenge/response depend on the password or could they enter the password after the initial request? E.g. User types in username, clicks login, gets challenge, types in response, types in password, clicks login again, and is authenticated?

If you can type the password in after the response, then all you would have to do is modify the basic page layout and SubmitButton_Click method. I’m guessing it could work like this (I haven’t actually tried this):

protected void Page_Load( object sender, EventArgs e )
{
  if(IsPostBack)
    return;
    
  PinTextBox.Visible = false;
  PasswordTextBox.Visible = false;
}

protected void SubmitButton_Click( object sender, EventArgs e )
{
    try
    {
      string userName = UsernameTextBox.Text;
    
    if(lblChallenge.Text == &amp;amp;quot;&amp;amp;quot;)
    {    
      string challenge = GenerateChallenge(userName);
      
      SaveChallengeOnServerSideSomeHow(challenge);

      PinTextBox.Visible = true;
      PasswordTextBox.Visible = true;
      
      return;
    }
    
    string response = PinTextBox.Text;
    
    if(!string.IsNullOrEmpty(response))
    {
      string challenge = GetChallenegeFromServerSideSomeHow();
      
      if(!ValidateResponse(userName, challege, response));
        throw new AuthenticationFailedException();
            
      SignIn(userName, PasswordTextBox.Text);
    }
    }
    catch ( AuthenticationFailedException ex )
    {
        HandleError( ex.Message );
    }
}

Milos Cekovic
Milos Cekovic Switzerland
11/27/2011 11:33:47 PM #

Hi Steve,
Thanks for the answer. The problem is that we need to authenticate with username and password first and then go to the second factor (i.e. challenge/response). I think the only possiblity here is to override the SignIn method. Do you know where I can find the information what exactly SignIn is doing and override examples?
Cheers, Milos

Steve Syfuhs
Steve Syfuhs Canada
11/28/2011 12:35:08 PM #

There is no documentation on how ADFS does it internally, but if you open up the assemblies with reflector you will see that ADFS generates a token based on the username and password and hands it off to an internal API to handle the authentication.

Unfortunately I have no idea how you can intercept that token.

Milos Cekovic
Milos Cekovic Switzerland
11/28/2011 11:23:07 PM #

I think the problem here is how to do a &quot;two-step&quot; two factor authentication. I need to let the user enter username and password on one page, then authenticate against AD, then go to another page and do the same with challenge/response. The problem is how to protect the second page being accessed without prior username/password authentication. Perhaps I can also stay on the same FormsSignIn.aspx and just hide and show controls. But then again, I have to make sure that I can not go to the challenge/response before I passed username/password. I could set a cookie to say that the user passed first factor. I would very appreciate your thoughts on this.

Milos Cekovic
Milos Cekovic Switzerland
11/29/2011 12:32:07 AM #

Would something like this work:

    protected void Page_Load( object sender, EventArgs e )
    {
        HttpCookie cookie = Context.Request.Cookies.Get(&quot;FactorOnePassed&quot;);
        if (null != cookie &amp;&amp; !String.IsNullOrEmpty(cookie.Value))
        {
      GetChallenge(UsernameTextBox.Text);
            plhChallenge.Visible = true;
      plhUsername.Visible = false;
        }
        else
        {
            plhChallenge.Visible = false;
      plhUsername.Visible = true;
        }
    }

  
    protected void SubmitButton_Click( object sender, EventArgs e )
    {
        try
        {
            HttpCookie cookie = Context.Request.Cookies.Get(&quot;FactorOnePassed&quot;);
            if (null != cookie &amp;&amp; !String.IsNullOrEmpty(cookie.Value))
            {
                SignInChallengeResponse(ChallengeLabel.Text, ResponseTextBox.Text);
                SignIn(UsernameTextBox.Text, PasswordTextBox.Text);
            }

            if (AuthenticateInAD(UsernameTextBox.Text, PasswordTextBox.Text))
            {
                cookie = new HttpCookie(&quot;FactorOnePassed&quot;, value);
                Context.Response.Cookies.Add(cookie);
            }
        }
        catch ( AuthenticationFailedException ex )
        {
            HandleError( ex.Message );
        }
    }

Steve Syfuhs
Steve Syfuhs Canada
11/29/2011 5:07:02 PM #

Functionally it would work, but security-wise you could bypass the second factor by just passing the cookie value. At that point it wouldn&#39;t really help much.

Milos Cekovic
Milos Cekovic Switzerland
11/29/2011 10:48:40 PM #

The last question:
If I would store the information, if the first factor is passed or not in the session rather than in the cookie, the security problem will be solved?

Steve Syfuhs
Steve Syfuhs Canada
12/1/2011 5:58:27 PM #

That could work. Dominick Baier has a better solution for storing state though, this way you aren't relying on session specifically: www.leastprivilege.com/...ookiesOnceAndForAll.aspx

Let me know how it turns out!

Pingbacks and trackbacks (1)+

Comments are closed

// About

Steve is a renaissance kid when it comes to technology. He spends his time in the security stack.