Mixing and matching multiple authentication styles in a single web application has always been difficult with WIF.
The new OWIN security components in ASP.NET change that, thanks to the finer grained control they grant over request processing pipelines.

One of the most common requests I have been hearing in the last couple of years is – how can one secure both UX controllers and API controllers within the same app? This post is meant to show you how to do exactly that.

I will start from an MVC app, already configured to handle web sign in via Azure AD thru OpenId Connect. I will add to the app a web API controller, show how to configure it to accepts calls secured via OAuth2 bearer token access from Azure AD, put together a quick test client and demonstrate how OpenID Connect and OAuth2 can coexist in the very same VS project. Pretty cool!

Set Up an App to Use OpenId Connect and Azure AD

Rather than starting from scratch, I recommend that you clone our basic web sign on sample from WebApp-OpenIDConnect-DotNet. Clone it and follow the instructions in the “How to run this sample” section in the readme. Once done, confirm that everything went well by hitting F5 and doing a test sign in. If everything works as described, you are ready to move to the next phase.

Add a Web API Controller

Next, we are going to add to the app’s controllers an ApiController and configure the app to correctly route requests through it. No identity work yet.

Right-click on the project in the solution explorer, choose add new/controller.

Choose Web API 2 Controller – Empty. I named mine AphorismController.

The scaffolding system goes as far as it can for adding the necessary bits to the project (the AphorismController.cs file, the WebApiConfig.cs file in App_Start) but it does not do everything for you; namely, there are some required changes in the global.asax.cs file that you have to apply manually. The good news is that the scaffolding helps you by listing those changes in a readme.txt that gets displayed as soon as it’s done updating the project. Specifically, you have to add to the Application_Start the following lines:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Web.Http;

namespace WebApp_OpenIDConnect_DotNet
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {            
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}

Once you’ve done that, we need to add an action to our new controller – just to see some action (no pun intended). Here there’s mine:

public class AphorismController : ApiController
{
    public string Get()
    {
        string [] aphorisms = 
            new [] {"Non ci sono piu' le mezze stagioni",
                    "Tanto va la gatta al lardo che ci lascia lo zampino",
                    "La twingo e' una macchina terribile"};
        return (aphorisms[new Random().Next(3)]);
    }
}

This web API will dispense a random tidbit of ancient Italian wisdom to anybody hitting via the browser the URL https://localhost:44320/API/Aphorism Winking smile.

Go ahead, hit F5 and give it a try now – before we start playing with identity  -to ensure that the controller was correctly added . Note that you can hit the API endpoint anonymously and get results regardless of whether you signed in the app via web sign in or not – that’s because the API controller we added is not secured yet.

Securing the Web API with Azure AD

Aaand here the fun begins. We know from past entries that the ASP.NET OWIN components include middleware specifically designed to secure web API via Azure AD and OAuth2 bearer token access. All we need to do here is to add the relevant middleware to the pipeline – ensuring that it does not step on the toes of the middleware already there (the OpenId Connect one, handling requests to the controllers which serve UX to the browser). That boils down to ensuring that the OpenId Connect middleware fires when the request involves UX, and that the Azure AD OAuth2 bearer token middleware fires only when the request is for a web API.

From the Package Manger Console, run

PM> Install-Package Microsoft.Owin.Security.ActiveDirectory -Pre
PM> Install-Package Microsoft.AspNet.WebApi.Owin -Pre

The first package contains the Azure AD OAuth2 middleware; the second contains classes used for using OWIN components with web API.

That done, head to Startup.Auth.cs and add the following highlighted code:

// ...
using Microsoft.Owin.Security.ActiveDirectory;
// ...
public void ConfigureAuth(IAppBuilder app)
{
    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

    app.UseCookieAuthentication(new CookieAuthenticationOptions());

    app.UseOpenIdConnectAuthentication(
        new OpenIdConnectAuthenticationOptions
        {
            Client_Id = clientId,
            Authority = authority,
            Post_Logout_Redirect_Uri = postLogoutRedirectUri
        });
    app.UseWindowsAzureActiveDirectoryBearerAuthentication(
        new WindowsAzureActiveDirectoryBearerAuthenticationOptions
        {
            Audience = "https://developertenant.onmicrosoft.com/WebUXplusAPI",
            Tenant = "developertenant.onmicrosoft.com",
            AuthenticationType = "OAuth2Bearer",                               
        });
}

The call to UseWindowsActiveDirectoryBearerAuthentication adds the relevant middleware to the pipeline. The tenant is the same we’ve used so far, and the Audience value comes straight from the App Id URI entry for the app in the Azure AD portal.
The notable bit here is the value assigned to AuthenticationType. Both the cookie and the OpenId Connect middlewares stick with the same default, assigned in the first line of ConfigureAuth; but the OAuth2 middleware gets a different value, to ensure that the activation paths of the two middleware groups remain distinct.

Here there’s how we take advantage of that. Head to your API controller class and add the following:

[HostAuthentication("OAuth2Bearer")]
[Authorize]
public class AphorismController : ApiController
{

The [Authorize] attribute establishes that only authenticated users are allowed to request any of the resources served by the controller. The value of HostAuthentication reflects the value specified in the initialization of the Azure AD OAuth2 web API middleware, creating a tie between the two and determining what middleware will fire upon receiving a request for this controller.

Go ahead and try hitting again the endpoint via browser: this time you’ll get back a 401.

Update the Azure AD Portal to Provision the Web API and Its Client

Currently we have a regression which requires you to do a bit of extra work for AD to recognize that, besides the UX, your web app exposes a web API. You can follow the instructions for adding a manifest entry to the API as presented in https://github.com/AzureADSamples/NativeClient-DotNet, step #3, register service.

Once that’s done, you have to create an entry for a client app to securely exercise the web API end point. You can follow the instructions in https://github.com/AzureADSamples/NativeClient-DotNet, step #, register client.

Create the Client Project

This is standard ADAL usage. Add to the solution a new project: I used a console app to kame things faster, but of course you can choose any time of client you like.

Once you have the project, get in the package manager console and run the two commands below.

PM> Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory –Pre
PM> Install-Package Microsoft.Net.Http -Pre

That’s pretty much it. The client is all boilerplate code, you can just paste the below in your project and simply change the AuthenticationContext construction and AccessToken call to use your actual settings.

using System;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net.Http;
using System.Net.Http.Headers;

namespace Client1
{
    class Program
    {
        static void Main(string[] args)
        {
            AuthenticationContext ac = 
                new AuthenticationContext("https://login.windows.net/developertenant.onmicrosoft.com");
            AuthenticationResult ar = 
                ac.AcquireToken("https://developertenant.onmicrosoft.com/WebUXplusAPI", 
                                "71aefb3b-9218-4dea-91f2-8b23ce93f387", 
                                new Uri("http://any"));

            string result = string.Empty;
            HttpClient httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Authorization = 
                new AuthenticationHeaderValue("Bearer", ar.AccessToken);
            HttpResponseMessage response = 
                httpClient.GetAsync("https://localhost:44320/API/Aphorism").Result;

            if (response.IsSuccessStatusCode)
            {
                result = response.Content.ReadAsStringAsync().Result;
            }
            Console.WriteLine(result);
            Console.ReadLine();
        }
    }
}

Test Run

Hit F5. The web app project will start. Either leave it as is or sign in, it makes no difference for the web API.

Next, right-click on your client project and choose debug/start new instance.

You’ll be prompted right away.

image

Sign in with a user from your development tenant, and voila’! You securely accessed via OAuth2 bearer token a web API, hosted alongside UX secured via OpenId Connect.

image

Wrap

This is one of the many scenarios that were difficult to achieve with the former programming model, but that we explicitly designed for in the new OWIN security components. In fact, as you have seen achieving peaceful coexistence of UX and API controllers in the same project is trivial with the new middleware.
I know for a fact that many of you will be super happy to read this Smile if you have feedback on how we can improve this further, don’t be shy!

 

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>