ASP.NET Core Integration Testing: Protected endpoints
Tags: testing, aspnetcore, csharp, dotnet
Published: 2019-10-07 08:17:59

---
title: ASP.NET Core Integration Testing: Protected endpoints
published: true
description: How to integration test protected API endpoints in ASP.NET Core using Cookie and JWT autentication schemes.
tags: testing, asp.net core, csharp, dotnet
series: ASP.NET Core Integration Testing
---

In this post I'll describe how you can test protected API endpoints.
I'll use the two most common scenarios: Cookie & JWT Authentication.

I have also created a public [repo](https://github.com/kai-oswald/IntegrationTestSample) with the full code. If you want to follow step by step you can also look at the commit history.

## Set up the `IntegrationTestInitializer`
If you've read my [introduction to integration testing in ASP.NET Core](https://dev.to/kaos/introduction-to-asp-net-core-integration-testing-1e77) you'll notice that we've changed the `IntegrationTestInitializer` quite a bit, because a lot of work has been put into `Microsoft.AspNetCore.Mvc.Testing` to allow for more configuration. By default the `TestServer` didn't handle cookies automatically, but when inheriting from `WebApplicationFactory<T>` the cookies are handled by default. This also cleans up the code.
{% link https://dev.to/kaos/introduction-to-asp-net-core-integration-testing-1e77 %}

```bash
> Install-Package Microsoft.AspNetCore.Mvc.Testing
```

```csharp
[TestClass]
public abstract class IntegrationTestInitializer : WebApplicationFactory<Startup>
{
    protected HttpClient _client;

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment("Testing")
            .UseStartup<Startup>();
        base.ConfigureWebHost(builder);
    }

    [TestInitialize]
    public void Setup()
    {
        var builder = new WebHostBuilder()
            .UseEnvironment("Testing")
            .UseStartup<Startup>();

        _client = this.CreateClient();
    }
}
```
Note that we've also set a custom environment `Testing` for our WebHost.
This will be used so we can decouple our testing code from our production code.

## Cookie Authentication

The cookie authentication setup in the API could look similiar to this:
```csharp
services.AddAuthentication(o => o.DefaultScheme = "Cookies")
    .AddCookie("Cookies", o =>
    {
        o.Cookie.Name = "auth_cookie";
        o.Cookie.HttpOnly = true;
        o.Events = new CookieAuthenticationEvents
        {
            OnRedirectToLogin = redirectContext =>
            {
                redirectContext.HttpContext.Response.StatusCode = 401;
                return Task.CompletedTask;
            }
        };
    });
```
This configuration basically sets up our HTTP Only Cookie scheme that returns a 401 on unauthenticated requests (by default a `302` redirect to the login page would be returned).

Then the login Method:
```csharp
[HttpPost("Login")]
public async Task<IActionResult> Login([FromBody] UserLoginModel user)
{
    ClaimsPrincipal claimsPrincipal = null;

    // Return a test user when environment is our Test environment
    if (_env.EnvironmentName == "Testing")
    {
        var claimsIdentity = new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, user.UserName)
        }, "Cookies");

        claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
    }
    else
    {
        // you should validate the userName and password here and load the specified user
        // We'll just return a default user to keep things simple...
    }
    await Request.HttpContext.SignInAsync("Cookies", claimsPrincipal);
    return NoContent();
}
```
Note that we just return a default user when we're in the test environment for now.
In a future post I'll also cover invalid users and a simple Database setup.

### Testing the unauthorized client
With the authentication method in tact, you should also test if the protected endpoints behave correctly when the client is unauthorized. We expect a HTTP status code of `401` if an unauthorized client tries to access an authorized endpoint.
```csharp
[TestMethod]
public async Task GetUsersUnauthorizedShouldReturn401()
{
    var response = await _client.GetAsync("api/users");

    Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
}
```

### Testing the authorized client
In C# the `HttpClient` handles cookies by default, so all we need to do now is to make a `POST` request to the `Login` method providing user credentials before we can access protected endpoints.
```csharp
private async Task PerformLogin(string userName, string password)
{
    var user = new UserLoginModel
    {
        UserName = userName,
        Password = password
    };

    var res = await _client.PostAsJsonAsync("api/account/login", user);
}
```
```csharp
[TestMethod]
public async Task CanGetUsers()
{
    List<string> expectedResponse = new List<string> { "Foo", "Bar", "Baz" };

    await PerformLogin("Test", "hunter2");

    var responseJson = await _client.GetStringAsync("api/users");
    List<string> actualResponse = JsonConvert.DeserializeObject<List<string>>(responseJson);

    CollectionAssert.AreEqual(expectedResponse, actualResponse);
}
```

## JWT Authentication

To set up JWT Authentication we have to add it as an Authentication Scheme in our `Startup.cs`
```csharp
// Configure Authentication
services.AddAuthentication(o => o.DefaultScheme = "Cookies")
    .AddCookie("Cookies", o =>
    {
        // omitted
    })
    .AddJwtBearer("Token", o => 
    {
        var key = Encoding.ASCII.GetBytes(appSettings.Secret);
        o.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false
        };
    });
```
I won't go into too much detail how to set up JWT Authentication, but you can look at the [GitHub repository](https://github.com/kai-oswald/IntegrationTestSample) containing the full source code. Note that we now have 2 valid Authentication methods active: Cookie & JWT, with `Cookies` being the default. So if we want to support both authentication methods we'd have to mark our Controller/Action with both authentication schemes.

```csharp
 [Authorize(AuthenticationSchemes= "Cookies,Token")]
```

### Testing the unauthorized client
Let's write our test with a random token where the authentication should fail.
```csharp
[TestMethod]
public async Task GetUsersJwtInvalidTokenShouldReturnUnauthorized()
{
    _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "invalid_token");

    var response = await _client.GetAsync("api/users");
    Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
}
```

### Testing the authorized client
To obtain a valid bearer token, we have to make a `POST` request to our token endpoint providing our credentials.

```csharp
private async Task<string> GetToken(string userName, string password)
{
    var user = new UserLoginModel
    {
        UserName = userName,
        Password = password
    };

    var res = await _client.PostAsJsonAsync("api/account/token", user);

    if(!res.IsSuccessStatusCode) return null;

    var userModel = await res.Content.ReadAsAsync<User>();

    return userModel?.Token;
}
```
Then we can set the token for the `Authorization` header on the test client.
```csharp
[TestMethod]
public async Task GetUsersJwtInvalidTokenShouldReturnUnauthorized()
{
    _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "invalid_token");

    var response = await _client.GetAsync("api/users");
    Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
}
```
## Conclusion

Now we can test Cookie and JWT protected API endpoints. 

In the next post I'll describe how you can test database access using an in-memory database approach.