Integrating Azure Active Directory Security Into NancyFx (part III)

Introduction

This is the third in a series of posts focusing on two .Net development technologies: Azure Active Directory (AAD) and NancyFx (aka Nancy). Nancy has emerged as a scalable web-development framework affording what it labels the "Super Duper Happy Path" (SDHP) for simplistic creation of web applications. AAD is a Azure/cloud-hosted directory service supplying authentication as a service (SaaS). We are securing a Nancy web application with AAD; the code presented in this series is available here.

Part III: Integrating Nancy Stateless Authentication with AAD

Mr Owl

This post covers further integration of the AAD configurations we created in part I with the Nancy web applciation we created and part II . We will be leveraging Nancy's support for stateless authentication to secure our web application's content. This affords a simplistic, low-overhead implementation to allow only trusted users access.

Handling AAD's Returned Authorization code

We ended part II with AAD invoking a callback to the configured RETURN_URL "/Authenticated" along with a query parameter representing an authorization code. Let's add functionality to the WebApp.csproj to use this authortization code to retrieve the identity of a user invoking our application.

Handling AAD's RETURN_URL Callback

In order to catch the callback redirection from AAD to "/Authenticated" create a new route within WebApp.csproj's SecureModule.cs:

        Get["/Authenticated"] = _ =>
        {
            return "Hello " + Context.CurrentUser.UserName + "!";
        };

If you F5'd the application you will enter into a redirect loop and never reach the inside of the "/Authenticated" route -- do you know why?

redirect loop

Remember the other code at the top of our SecureModule() :

        Before += ctx =>
        {
            return ctx.CurrentUser == null ||
                   String.IsNullOrWhiteSpace(ctx.CurrentUser.UserName)
                ? new RedirectResponse("/login")
                // else allow request to continue unabated
                : null;
        };

Even though AAD is dialing back to our /Authenticated route with an authorization means that the current user was identified by AAD as being authorized for access - Nancy still hasn't associated the incoming request (from AAD) with an authorized user. Because of this AAD's callback (and authorization code) will be rebuffed and redirected back to the /login route. How ironic!


irony

In order to fix this unexpected behavior we will call in some backup in the form of a Nuget package.

Integrating Nancy's Stateless Authentication

We will use Nancy's stateless authentication to consume the authorization code returned by AAD to determine a user's identity. Once Nancy knows an incoming request is associated with a real user (or not) application security logic is breezy; however there's a little more work involved to before we get there.

  1. Add the "Nancy.Authentication.Stateless" Nuget package to WebApp.csproj. This package adds support for authenticating a request eachtime it is handled by Nancy without the overhead of database or session persistance.
  2. Create a new class within WebApp.csproj called Bootstrapper.cs containing the following:

bootstrapper

I apologize for the image in place of code - ghost blogging hates my code formatting -- but there is a link to all the source code at the end

The function of this code is to override Nancy's default behavior in order to inject stateless authentication into every request that the application handles. The RequestStartup method is invoked prior to every request that Nancy handles.

The interesting part comes here:

var authorizationCode = (string)nancyContext.Request.Query.code;
return AADHelper.GetAuthenticatedUserIDentity(authorizationCode);

We are grasping the authentication code (remember -- that AAD returned to us) and using it to retrieve a user identity from AAD. Unfortunately the magic method we call here to do that doesn't yet exist. Let's write it.

Retrieving Identity from AAD

I already let the cat out of the bag - that mysterious authentication code that AAD sent us back in part II provides the ability to retrieve a user's identification. That's its secret. Mystery solved. Let's use the token (and AAD) to do just that.

  1. Add the Nuget package Microsoft.IdentityModel.Clients.ActiveDirectory to WebApp.csproj. This is the Active Directory Authentication Library (ADAL) -- the .Net library for consuming AAD's SaaS offerings. You can conceptualize ADAL as a telephone for requesting AAD information from within a consuming application.
    • You might say "wait a second we already used AAD to show our user a login screen without ADAL!" Well we sort of did; if you look back to part II what we actually did was hack an oauth2 URI containing our AAD tenant.
  2. Open the AADHelper.cs class and add a using reference to Microsoft.IdentityModel.Clients.ActiveDirectory.
  3. Add a new method at the bottom of the class:
    GetAuthenticatedUserIdentity

Before proceeding let's resolve the missing reference to this method's UserIdentity dependency. Add a new class to WebApp.csproj named UserIdentity.cs:

using Nancy.Security;
using System.Collections.Generic;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace WebApp
{
    public class UserIdentity : IUserIdentity
    {
        public UserIdentity(UserInfo userInfo)
        {
            UserName = userInfo.UserId;
        }

        public string UserName { get; set; }
        public IEnumerable<string> Claims { get; set; }
    }
}

This class is just an adapter which converts ADAL's UserInfo object into an implementation of IUserIdentity -- an interface which Nancy works off of for authentication.

Now we're all hooked up to retrieve a user's identity from AAD using the authorization token provided by AAD and then integrate it into Nancy's stateless authentication. Let's watch it in action.

Let it Rip

  • Within the WebApp.csproj add a Visual Studio breakpoint to the following line of Bootstrapper.cs:
    breakpoint
  • F5 the application and navigate to http://localhost:1234/Private. Your previous login credentials should be cached within your browser (if not login with the test user we configured back in part I). You will hit the above breakpoint after AAD has authorized you and passed back an authorization code thusly:
    breakpoint2
  • F11 to step into the GetAuthenticatedUserIDentity method:

    breakpoint3

    As you continue to the end of the method you'll see that we have UserInfo returned from AAD. We convert this into a UserIdentity object (ala our adapter) and return it from the method.

  • Press Shift+F11 to return to Bootstrapper.cs

    breakpoint4

    From the context of Nancy's stateless authentication we are consuming the returned UserIdentity and associating it with the current HTTP request (remember we are in the RequestStartup method that is invoked prior to each handled request).

  • Press F10 to continue along the request's path through Nancy. Since the destination is /Authenticated (the RETURN URL configured within AAD back in part I) - the request will next be handled within SecureModule.cs.

Remember the Before += ctx => code that is invoked within SecureModule before a request is handled in order to redirect unauthenticated requests (see part II)?

breakpoint5

This time around Nancy has a user associated with the incoming request. This is the user we configured within our AAD Tenant back in part1.

have we met before?

Because Nancy has an authenticated user associated with the request it continues unabated through Before += ctx => and on to its destination (as we configured within AAD as the RETURN URL back in part I).

  • The request ends up here:

breakpoint6

And outputs the user's name to the browser window:

output

Congratulations you've just setup a Nancy web application with stateless authentication to leverage Azure Active Directory's authentication service!

Extra Credit

Handling Errors From AAD

Add the following code to the top of SecureModule's Before += ctx => to handle errors returned from ADAL's AcquireTokenByAuthorizationCode method to aid debugging:

            if (ctx.Request.Query.error.HasValue)
            {
                string errorDesc = 
                    string.Format("{0}\n\n{1}\n\n{2}",
                    ctx.Request.Query.error,
                    ctx.Request.Query.error_description);

                Context.Response            = Response.AsText(errorDesc);
                Context.Response.StatusCode = HttpStatusCode.Forbidden;
                return Context.Response;
            }                

Conclusion

In this post we integrated the work from part I and part II to support stateless authentication within Nancy using AAD and Microsoft's ADAL library. You can obtain the source code we created in this series from this git repo. Where possible obfuscated complexity (and confusion) that being said there is a lot going on; hoprfully this series of posts will give you a good introductory context to dive deeper and learn more about authorization, authentication, AAD and NancyFx.