Authenticated ASP.NET Web API Integration Testing hosted by OWIN/Katana

This post is backed up from my old blog and was posted at 2014-12-09.

I had to add some integration tests to a Web API which was secured by an [Authorize]-attribute. I’m using Thinktecture Identity Server v3 for authentication and authorization using an OIDC Hybrid Flow. The steps for testing this would be:

  1. Clean Identity Server database (in case of it is not empty yet)
  2. Host Identity Server in-memory with a special user for testing
  3. Do everything according to OIDC Hybrid Flow to get a correct login
  4. Host Web API in-memory
  5. Throw some URLs against the Web API for testing
  6. Dispose Web API in-memory hosting
  7. Dispose Identity Server in-memory hosting
  8. Clean Identity Server database

Since my Web API doesn’t use a database, there is no step for cleaning. If you use one, don’t forget to clean it up to avoid side effects. In my opinion doing the first three steps just felt not right. I want to test the Web API and not, if Identity Server and the [Authorize]-attribute works correctly (this is done by the project’s test itself).
To get rid of those steps, a fake “login” has to be created by implementing a DelegatingHandler:

public class WebApiTestingHandler : DelegatingHandler
{
    private const string TestingHeader = "X-Integration-Testing";
    private const string TestingHeaderValue = "25bec631-4b0a-4910-8431-067ec41ef65a";

    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
#if DEBUG
        var hostHeader = request.Headers.GetValues("Host").First();

        if (hostHeader == "localhost")
        {
            IEnumerable<string> testingHeader;

            if (request.Headers.TryGetValues(TestingHeader, out testingHeader))
            {
                if (testingHeader.FirstOrDefault() == TestingHeaderValue)
                {
                    var dummyPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new[] {new Claim("sub", "IntegrationTesting")}, "Testing"));

                    Thread.CurrentPrincipal = dummyPrincipal;
                    request.GetOwinContext().Authentication.User = dummyPrincipal;
                }
            }
        }
#endif

        return await base.SendAsync(request, cancellationToken);
    }
}

It’s important, that this handler does not go into production code, so I’ve used a compiler condition, which will only apply the testing code, if the project is build with debug settings.

Side note: If you need to test your project with a release build, you still can do this. But be sure that you don’t deploy it to your production server. Otherwise it could be possible, someone figures out, how to bypass your authentication.

If build in debug mode, the handler will check, if the request host is localhost. If so, it’ll check for a special header “X-Integration-Testing” and, if set, checks for a special value. If everything is set, it will create a ClaimsPrincipal and assigns it to the current thread and OWIN context.

Side note: You have to set the authentication type of the claims principal (“Testing”), otherwise it will not be counted as logged in.

You can now add the handler to your HttpConfiguration when setting up OWIN hosted Web API:

public class Startup   
{
    public void Configuration(IAppBuilder app)
    {
        var httpConfiguration = CreateHttpConfiguration();
        app.UseWebApi(httpConfiguration);
    }

    private HttpConfiguration CreateHttpConfiguration()
    {
        var configuration = new HttpConfiguration();
        
        configuration.MapHttpAttributeRoutes();

#if DEBUG
        configuration.MessageHandlers.Insert(0, new WebApiTestingHandler());
#endif

        return configuration;
    }
}

Again: Make sure the handler is only added in debug mode. When this is done, you can start creating some tests, which bypass the actual authorization but will test your Web API making “real” HTTP calls.

In my case, I’m using xUnit.net and FluentAssertions. Sample test:

public class MyAuthorizedControllerTest : IDisposable
{
    #region Test Setup / Tear down

    private readonly TestServer _webApi;

    #region IDisposable

    private bool _disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _webApi.Dispose();
            }

            _disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    public OrdersControllerTest()
    {
        _webApi = TestServer.Create();
    }

    #endregion

    private HttpClient CreateHttpClient()
    {
        var httpClient = new HttpClient(_webApi.Handler)
        {
            BaseAddress = new Uri("http://localhost")
        };

        httpClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Integration-Testing", "25bec631-4b0a-4910-8431-067ec41ef65a");

        return httpClient;
    }

    [Fact]
    public async void Hello_returns_hello_world()
    {
        var httpClient = CreateHttpClient();

        var response = await httpClient.GetAsync("/api/hello");

        response.StatusCode.Should().Be(HttpStatusCode.OK);

        var content = await response.Content.ReadAsStringAsync();

        content.Should().Be("Hello World!");
    }
}

What’s happening here?

At first, it’s xUnits.net’s convention, that the test class is instantiated for every [Fact]. So we spin up an OWIN TestServer every time. To dispose it correctly, we need to implement IDisposable which will be used by xUnit.net automatically.

Side note: To use OWIN TestServer you need to install Owin.Testing nuget package:

Install-Package Microsoft.Owin.Testing

The test itself basically created a new HttpClient (be use to use the HttpHandler from TestServer) with localhost as its url. Then it adds our special integration testing header with it’s correct value for bypassing [Authorize].

We now use the httpClient to create a HTTP GET request and check if the response is what we want.

For completeness, the Web API controller:

[Authorize]
public class HelloWorldController : ApiController 
{
    [HttpGet]
    [Route("api/hello")]
    public string Get() 
    {
        return "Hello World!";
    }
}