Protecting a Self-Hosted Nancy API with Microsoft.Owin.Security.ActiveDirectory

04 Jan 2014

This post is the Nancy version of Vittorio's "Protecting a Self-Hosted API with Microsoft.Owin.Security.ActiveDirectory". Since I'm lazy, I may even be lifting some parts of it verbatim, if you don't mind, Vittorrio ;)

I'm going to skip the intro to Owin etc, if you are reading this, you probably know all about it by now. (Except for you Phillip; Get yout s**t together man!). This tutorial will be using Nancy.MSOwinSecurity that I introduced in the previous post.

What I am going to show you is how you can set up a super simple Nancy HTTP API in a console app and how you can easily secure it with Windows Azure AD with the exact same code you use when programming against IIS Express/full IIS.

Here's what we are going to do:

  • Create a minimal self-hosted Nancy Http API in a console app.
  • Add middleware for validating JWT tokens from AAD.
  • Create a console app client to poke our API and challenge the user for credentials.

Create a Minimal Self-Hosted Nancy Http API

Let's start by creating blank solution. I'm a bit unimaginative so I am calling this 'NancyAAD'. Note: this entire solution will be targeting .NET 4.5. .NET 4.0 is not supported. Next, we add a console application, 'NancyAAD.Server', that is going to host our Nancy HTTP API. To this project, we're going to add a number of nuget packages, in this order:

  1. Nancy.Owin - This will bring in Nancy and the adapter that allows Nancy to be hosted in an OWIN pipeline.
  2. Microsoft.Owin.Hosting - Provides default infrastructure types for hosting and running OWIN-based applications. There are other hosting implementations out there, if you feel the need to explore.
  3. Microsoft.Owin.Host.HttpListener - OWIN server built on the .NET Framework's HttpListener class.
  4. Microsoft.Owin.Security.ActiveDirectory - The middleware through which we will secure our Nancy HTTP API. This package will pull in a bunch of other packages. We'll be using this later.
  5. Nancy.MSOwinSecurity - This will provide integration between Nancy's context and modules and Microsoft.Owin.Security.*.

Your NancyAAD.Server packages.config should look similar to this:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.Owin" version="2.0.2" targetFramework="net45" />
  <package id="Microsoft.Owin.Host.HttpListener" version="2.0.2" targetFramework="net45" />
  <package id="Microsoft.Owin.Hosting" version="2.0.2" targetFramework="net45" />
  <package id="Microsoft.Owin.Security" version="2.0.2" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.ActiveDirectory" version="2.0.2" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.Jwt" version="2.0.2" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.OAuth" version="2.0.2" targetFramework="net45" />
  <package id="Nancy" version="0.21.1" targetFramework="net45" />
  <package id="Nancy.MSOwinSecurity" version="1.0.0" targetFramework="net45" />
  <package id="Nancy.Owin" version="0.21.1" targetFramework="net45" />
  <package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" />
  <package id="Owin" version="1.0" targetFramework="net45" />
  <package id="System.IdentityModel.Tokens.Jwt" version="1.0.0" targetFramework="net45" />
</packages>

Next we'll define our Nancy module and a very simple GET endpoint that we'll want to secure later:

namespace NancyAAD.Server
{
    using Nancy;

    public class ValuesModule : NancyModule
    {
        public ValuesModule()
        {
            Get["/values"] = _ => new[] { "value1", "value2" };
        }
    }
}

That done, let's add the Startup class for hosting our Nancy application in OWIN:

namespace NancyAAD.Server
{
    using Owin;

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseNancy();
        }
    }
}

This says that Nancy is handling all requests from the root. Nice and simple.

Lastly, we need to host the OWIN application itself. In Program.cs, we write the following:

namespace NancyAAD.Server
{
    using System;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using Microsoft.Owin.Hosting;

    internal class Program
    {
        public static void Main(string[] args)
        {
            using (WebApp.Start<Startup>("http://localhost:9000/"))
            {
                Console.ForegroundColor = ConsoleColor.Blue;
                Console.WriteLine("Nancy listening at http://localhost:9000/");

                // Test call
                var client = new HttpClient();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                var response = client.GetAsync("http://localhost:9000/values").Result;
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(response.Content.ReadAsStringAsync().Result);

                Console.ReadLine();
            }
        }
    }
}

The call to WebApp.Start initializes a new server, which listens at the specified address. The rest of the method calls the Nancy HTTP API to double check that we did everything correctly.

There is one subtle difference here compared to Vittorio's post - we are defining an Accept header "application/json". The reason is that because Nancy supports view engines where it's default behaviour when returning a model for a request with no accept header, is to try to resolve a view. This would result in an exception for us as we haven't defined any views.

F5'ing the app should result in:

Console

Exactly the same as Vittorio's screen shot. Nice.

Before moving on to the next task, remove the test call from the Main method and change it to the following:

public static void Main(string[] args)
{
    using (WebApp.Start<Startup>("http://localhost:9000/"))
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine("Nancy listening at http://localhost:9000/");
        Console.WriteLine("Press ENTER to terminate");
        Console.ReadLine(); 
    }
}

Secure the Nancy HTTP API with Windows Azure AD

Here comes the raison d’être of the entire post. We have already added the Microsoft.Owin.Security.ActiveDirectory nuget package, now let's add it in the right place into the OWIN pipeline. Note, the order things are defined in the pipeline are important, so we must add this before Nancy so requests are piped through it before hitting the Nancy handlers.

Our OWIN pipeline with the middleware added now looks like:

public void Configuration(IAppBuilder app)
{
    app.UseWindowsAzureActiveDirectoryBearerAuthentication(
            new WindowsAzureActiveDirectoryBearerAuthenticationOptions
            {
                Audience = "https://contoso7.onmicrosoft.com/RichAPI",
                Tenant = "contoso7.onmicrosoft.com"
            })
          .UseNancy();
}

The new code is the "UserWindowsAzure....". You’ll notice that instead of relying on app.config settings, values are passed directly to the middleware. This is another beauty of OWIN, you can now use can keep such settings wherever you deem most appropriate.

Now our pipeline includes the right middleware: if we receive a JWT, we’ll validate it and if it checks out we’ll pass the corresponding ClaimsPrincipal to the handler delegate. Very good, but not good enough. Let’s modify the module class to mandate that all callers must present a valid token from the tenant of choice:

public void Configuration(IAppBuilder app)
{
    app.UseWindowsAzureActiveDirectoryBearerAuthentication(
            new WindowsAzureActiveDirectoryBearerAuthenticationOptions
            {
                Audience = "https://contoso7.onmicrosoft.com/RichAPI",
                Tenant = "contoso7.onmicrosoft.com"
            })
          .UseNancy();
}

The new code is the "UserWindowsAzure....". You’ll notice that instead of relying on app.config settings, values are passed directly to the middleware. This is another beauty of OWIN, you can now use can keep such settings wherever you deem most appropriate.

Now our pipeline includes the right middleware: if we receive a JWT, we’ll validate it and if it checks out we’ll pass the corresponding ClaimsPrincipal to the handler delegate. Very good, but not good enough. Let’s modify the module class to mandate that all callers must present a valid token from the tenant of choice:

public class ValuesModule : NancyModule
{
    public ValuesModule()
    {
        this.RequiresMSOwinAuthentication();
        Get["/values"] = _ =>
        {
            ClaimsPrincipal claimsPrincipal = Context.GetMSOwinUser();
            Console.WriteLine("==>I have been called by {0}", claimsPrincipal.FindFirst(ClaimTypes.Upn));
            return new[] {"value1", "value2"};
        };
    }
}

The key statement here is "this.RequiresMSOwinAuthentication" - this ensures that a request hitting this module must have a valid authenticated user, otherwise an unauthorized HTTP status code is returned.

The second key statement, "Context.GetAuthenticationManager().User", retrieves the user from IAuthenticationManager where we write a claim to the console, just for demonstration purposes. The UPN claim is among the ones that Windows Azure AD sends in JWTs.

Believe it or not, that’s all we had to do to secure the Nancy HTTP API: we just had to add the code in bold :)

Create a Client App and Test the Nancy HTTP API

Add a new console app project called NancyAAD.Client (see, I'm not really imaginative!). To this project, install the 'Microsoft.WindowsAzure.ActiveDirectory.Authentication' nuget package. This package contains the main assembly for the Windows Azure Authentication Library (AAL) and provides easy to use authentication functionality for .NET client apps. Your packages.config should look like;

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.WindowsAzure.ActiveDirectory.Authentication" version="0.7.0" targetFramework="net45" />
</packages>

Next up, the Main method should looks like this:

namespace NancyAAD.Client
{
    using System;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using Microsoft.WindowsAzure.ActiveDirectory.Authentication;

    internal class Program
    {
        [STAThread]
        public static void Main(string[] args)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Client ready.");
            Console.WriteLine("Press any key to invoke the service");
            Console.WriteLine("Press ESC to terminate");
            ConsoleKeyInfo consoleKeyInfo;

            var authenticationContext = new AuthenticationContext(
                "https://login.windows.net/contoso7.onmicrosoft.com");

            do
            {
                consoleKeyInfo = Console.ReadKey(true);
                // get the access token
                AuthenticationResult authenticationResult = authenticationContext.AcquireToken(
                    "https://contoso7.onmicrosoft.com/RichAPI",
                    "be182811-9d0b-45b2-9ffa-52ede2a12230",
                    "http://whatevah");
                // invoke the Nancy API
                var httpClient = new HttpClient();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                httpClient.DefaultRequestHeaders.Authorization =
                    new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
                HttpResponseMessage response = httpClient.GetAsync("http://localhost:9000/values").Result;
                // display the result
                if (response.IsSuccessStatusCode)
                {
                    string result = response.Content.ReadAsStringAsync().Result;
                    Console.WriteLine("==> Successfully invoked the service");
                    Console.WriteLine(result);
                }
            } while (consoleKeyInfo.Key != ConsoleKey.Escape);
        }
    }
}

This is a very classic ADAL client and is virtually identical to the one in Vittorio's post. It simply acquires the token, attaches it to the Authorization header before making the call. The one difference is the Accept header is defined, as explained previously.

Starting both projects and triggering a call from the client should result in:

Login prompt

Sign in as any user in your tenant, and you’ll get to something like the screen below:

Console

Ta-dah!

Now go forth and secure your enteprise(!) Nancy Apps!

Full code for this is on Github.