Mobile Authentication for a Web Site with a YubiKey NEO

I keep up with what gets posted on Channel 9 (lot’s of good stuff there), and a few months back I watched a demo which really is quite the smooth solution:
http://channel9.msdn.com/Shows/Web+Camps+TV/Benjamin-Soulier-Logs-into-a-Web-Site-Using-a-Bar-Code-and-Windows-Phone

What they show is how a normal web site presents a QR code for login, and when you scan the QR code with an app on your Windows Phone 8 device you are authenticated, and the web site refreshes it’s view to show that you are now logged in! (The web site is shown on a computer where there is no direct communications channel to or from the mobile device.) You gotta watch it to understand it. (Forward to around the 15-minute mark for the actual demo.)

Unfortunately there isn’t any sample code to follow the video, so you can’t just download a module and install in your own solution. While I don’t know the specifics of the implementation there were a few key words that got me thinking about how it could be done.

About a year ago I did two posts on a product called YubiKey:
http://mobilitydojo.net/2012/05/09/two-factor-authentication-on-mobile-devices/
http://mobilitydojo.net/2012/05/14/active-directory-federation-services-and-yubikeys/

Nice product, and I use it for some of my authentication needs, but I never got around to doing anything really snazzy with it. So I thought I’d see if the YubiKey could be used for a scenario similar to the one in the video.

Semi-long post, so you might want to grab a cup of coffee before going into the details. If you’re in a hurry just skip to the bottom of the post where there’s a video showing the end result 🙂

There are a couple of different versions of the YubiKey depending on what you need. (Check my first post from last year for more details.) For the purposes of this post I’ll be using the NEO, and more specifically the NFC part of it. This of course also means you will need a phone with NFC support which does rule out the iPhone for the time being, but comes supported in both Android and Windows Phone. (I tested both with a Galaxy Nexus and the HTC 8x.)

The YubiKey is preprogrammed to work with a demo server provided by Yubico, so you will need to reconfigure the YubiKey to work with your infrastructure. We’ll get back to that after doing some coding first 🙂

Create a new project in Visual Studio, select the "ASP.NET MVC 4 Web Application" type. I’ll name my solution "YubiR":
VS_01

Select the "Internet Application" template:
VS_02

It is possible to work with other templates as well, possibly even better when making things proper, but this will work for our proof-of-concept. (The reason we choose this for now is to have all the forms authentication stuff included automatically.)

Open Tools->Library Package Manager->Package Manager Console, and run the following command "install-package Microsoft.AspNet.SignalR" (or lookup "Microsoft ASP.NET SignalR” in the NuGet UI if you prefer):
VS_03

You may or may not be familiar with SignalR already. I’ll quote from Microsoft themselves (http://www.asp.net/SignalR):
"ASP.NET SignalR is a new library for ASP.NET developers that simplifies the process of adding real-time web functionality to your applications. Real-time web functionality is the ability to have server-side code push content to connected clients instantly as it becomes available."

Which might just sound like yet another framework they want you to learn I suppose 🙂 Test http://shootr.signalr.net/ for a quick demo of why it’s not such a bad thing to learn. (Yes, I know it builds on HTML5 standards and you can do the same without SignalR, but if I can use libraries that simplify things for me, and make it work across several browsers, I’m not one to turn down the opportunity.)

So, with the project loaded it’s time to write some code. First open up Views->Home->Index.cshtml and remove everything but the title and the featured section. Then add an if statement for showing one text when user is authenticated, and another text if the user is not authenticated. It’ll look like this:

@{
    ViewBag.Title = "YubiR";
}
@section featured {
    <section class="featured">
        <div class="content-wrapper">
            <hgroup class="title">
                <h1>@ViewBag.Title.</h1>                
            </hgroup>
            <p>
                This demo works best with a Yubikey NEO.
            </p>
        </div>
    </section>
}

@if (!User.Identity.IsAuthenticated)
{    
    <h3>Please login to see content.</h3>   
}

@if (User.Identity.IsAuthenticated)
{    
    <h3>Welcome back @User.Identity.Name</h3>   
} 

I also edited a few texts in Views->Shared->_Layout.cshtml (title, logo & footer) which technically isn’t required but makes it feel slightly personalized. It looks like this:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>@ViewBag.Title - My Yubikey Auth Web Site</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
        <meta name="viewport" content="width=device-width" />
        @Styles.Render("~/Content/css")
        @Scripts.Render("~/bundles/modernizr")
    </head>
    <body>
        <header>
            <div class="content-wrapper">
                <div class="float-left">
                    <p class="site-title">@Html.ActionLink("MobilityDojo.net", "Index", "Home")</p>
                </div>
                <div class="float-right">
                    <section id="login">
                        @Html.Partial("_LoginPartial")
                    </section>
                    <nav>
                        <ul id="menu">
                            <li>@Html.ActionLink("Home", "Index", "Home")</li>
                            <li>@Html.ActionLink("About", "About", "Home")</li>
                            <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
                        </ul>
                    </nav>
                </div>
            </div>
        </header>
        <div id="body">
            @RenderSection("featured", required: false)
            <section class="content-wrapper main-content clear-fix">
                @RenderBody()
            </section>
        </div>
        <footer>
            <div class="content-wrapper">
                <div class="float-left">
                    <p>&copy; @DateTime.Now.Year - My Yubikey Auth Web Site</p>
                </div>
            </div>
        </footer>

        @Scripts.Render("~/bundles/jquery")
        @RenderSection("scripts", required: false)
    </body>
</html>

At this point you should launch your site (press F5) just to check that things are working. The index page should state that you should login to see content:
IE_01

If you attempt to login you will be informed that the credentials are incorrect:
IE_02

Go ahead and register a user (we will be using this username afterwards as well so do remember it).
IE_03

You’ll be redirected to the Index page, and it will show the authenticated content:
IE_04

Quit the browser and return to Visual Studio. You will now add some SignalR components. Right-click the project name and add a folder called "Hubs" (Add->New Folder).

Create a new class in this folder (Add->Class) and call this "AuthHub.cs". Replace the contents of the class with the following code:

  
using Microsoft.AspNet.SignalR;

namespace YubiR
{
    public class AuthHub : Hub
    {
        public void AuthYubi(string userName, string userId)
        {           
            Clients.All.authYubi(userName, userId);
        }

        public void RefreshPage()
        {
            Clients.All.refresh();
        }
    }
}

We don’t want to mess up the existing login/register mechanism so we’ll create an API controller for this purpose. Right-click the "Controllers" folder, Add->Controller, and call it AuthController. Make sure "Empty API controller" is the template.

>VS_04

We’ll add a couple of methods; "Yubi" which will accept the inputs from a browser, and "authOTP" which will call back to Yubico’s server to verify the OTP.

AuthController should look like this:

   
using Microsoft.AspNet.SignalR;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Security;

namespace YubiR.Controllers
{
    public class AuthController : ApiController
    {
        [HttpGet]
        [AllowAnonymous]
        public HttpResponseMessage Yubi(string otp)
        {                       
             if (authOTP(otp))
            {
                //Uncomment the line below if you want the calling browser to be authenticated as well
                //FormsAuthentication.SetAuthCookie("Andreas", true);                                                              

                //Need a context for the SignalR hub
                var ctx = GlobalHost.ConnectionManager.GetHubContext<AuthHub>();

                //Signal the browser clients 
                //Substitute "Andreas" With the username you registered earlier
                ctx.Clients.All.authYubi("Andreas", "DummyID");
                ctx.Clients.All.refresh();

                return new HttpResponseMessage()
                { 
                  Content = new StringContent("<h3>You have been successfully authenticated.</h3>", System.Text.Encoding.UTF8, "text/html")
                };                
            }
            else
            {
                return new HttpResponseMessage()
                {
                    StatusCode = HttpStatusCode.Forbidden                    
                }; 
            }
        }

        [HttpGet]
        [AllowAnonymous]
        public string Yubi(string userName, string userId)
        {
            FormsAuthentication.SetAuthCookie(userName, false);
            return "Status:Success";
        }

        private bool authOTP(string otp)
        {            
            string OTP = otp;
	  //You need to acquire an API key from Yubico.com
            string authId = "xyz";
            string server = "https://api.yubico.com/wsapi/verify";

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

            bool 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 (Exception)
            {
                //ToDo: Handle Errors
            }

            return result;
        }
    }
}

You’ll notice there’s also an overload of “Yubi” with two parameters. This is a slightly hackish approach, but the thing is that we need the browser that is being logged in to get the authentication token; not the mobile device I’m using for the actual authentication. So after the mobile device triggers a successful authentication the computer’s browser is performing a "silent login” to acquire the token. Would I say this is bulletproof security wise? No, but I would probably do things slightly different for a production setup anyways so for the demo effect this is ok.

Most of the stage is set now, and we can return to index.cshtml to add some Javascript to handle the front-end for the SignalR work that happens in the background. We’ll add a scripts section:

   
@section scripts {
    <script src="~/Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="~/signalr/hubs"></script>

    <script>
        $(function () {           
            var auth = $.connection.authHub;
            
            auth.client.authYubi = function (username, userid) {                            
                $.ajax({
                    url: '/api/auth/yubi',
                    data: {"userName":username,"userId":userid},
                    type: 'GET',
                    datatype: 'json',
                    success: function(data) {},
                    error: function() {alert('Error');}
                });
            };           

            auth.client.refresh = function () {
                //If we don't set a delay the page will reload before auth is completed
                window.setTimeout(function(){window.location.reload()},3000);
            };

            $.connection.hub.start().done(function () {                                
            });            
        });
    </script>
}

You’ll see a $.ajax call to the web api as mentioned in the previous paragraph. There’s also a refresh so that we get logged in automatically instead of having to hit refresh.

A small tweak to global.asax.cs is needed as well. Add "RouteTable.Routes.MapHubs();” as the first line in Application_Start – like this:

using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace YubiR
{   
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            RouteTable.Routes.MapHubs();
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();
        }
    }
} 

Feeling lucky I am publishing the site to the Internet at this stage. (I’ll show you how to test/debug things locally afterwards.) I created a Web Site in Windows Azure (they’re free) and followed the publish wizard in Visual Studio (right-click the project and select "Publish".)
VS_05

Remember to create a database, and make sure it’s selected in the publish wizard. A minor snag I ran across with the free tier of Azure Websites you might want to look out for is that redeploying, like you often do when debugging, will kill your quota in no time, and then you have to wait for it to reset or upgrade your tier… (Of course you don’t have to deploy to Azure Websites. You can deploy to Azure PaaS, "plain old" IIS, etc. if you prefer that instead.)

The YubiKey’s default configuration points to
http://demo.yubico.com/php-yubico/one_factor.php?key=OTP, and if you tap the chip onto the back of your device you’ll get a browser telling you all is good (if there’s nothing wrong with the NEO of course).
WP_01

Time to re-configure this part of the NEO. You’ll need to download the configuration utility from Yubico: http://www.yubico.com/products/services-software/personalization-tools/use/
(I went with the Windows build of the Cross-Platform Personalization Tool.)
Find the "Tools" tab which should bring up "NDEF Programming" as the main option.
Select the configuration slot to use (I have a pre-production NEO so I’ve only got 1 slot), and make sure "URI" is the chosen NDEF type.

For the payload you have to know the address the server will listen to. I deployed my site to http://yubir.azurewebsites.net, and since I went with the default routing for my Web API the address the device needs to do an HTTP GET against is
http://yubir.azurewebsites.net/api/auth/Yubi?otp=
.The key will append the OTP value to the query string.
Yubi_01

If you don’t have the hardware to test this you can still get some of the wow effect. Edit the "authOTP" method in AuthController.cs so it just contains "return true;" – that way the Yubico servers are never used. Do a deploy to IIS Express, an actual server, or whatever you like. Open two browser windows; on separate computers if you can. Browser one can be left at the Index page, and in browser two you type in
http://fqdn:xyz/api/auth/yubi?otp=randomtext in the address bar and hit Go. After the browsers have had time to do their magic you’ll notice the index page should switch to greeting a welcome back.

If you use a computer and a device it’ll look like this:
The browser before authentication
RT_01

The browser after authentication
RT_02

Remember – after opening the web site in the browser I do not touch it at all. It’s all automagic.

On a Windows Phone 8 device doing the authentication (with Norwegian language, but you still get the picture I hope):
After the NFC tag has been registered by the device. (“Godta” means “Accept”.)
WP_02

Feedback from our server
WP_03

I usually only include screenshots in my posts, but I suppose this kind of requires video evidence to show off the full effect.

 

How’s that for snazzy logins?

I know there are shortcomings to the solution as it stands now.
There is no multi-tenancy.
The login is broadcasted to all clients connected to the web site, which you usually don’t want.
There is only one-factor login, and not everything is according to security best practices.
But it is something that works and demonstrates that it’s not all that much work to get started. (While I have taken shortcuts there is an actual proper authentication taking place.)

Leave a Reply

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

*