Basic authentication of .NET 6.0 Web API
Published: 2022-04-17 16:09:34

# Summary
In this article, I would share a .NET 6.0 Web API sample code that supports Basic Authentication. Here is [the sample code](https://github.com/koheikawata/api-management-sample/tree/main/src/api/BasicAuth)

# TOC
- [Concept](#concept)
  - [Configuration](#configuration)
  - [Custom attribute](#custom-attribute)
  - [Middleware](#middleware)
  - [Allowanonymous attribute](#allowanonymous)
- [Sample request](#sample-request)

# Concept <a name="concept"></a>

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qwpkaw1ivh1hj2whbseo.png) 

## Configuration <a name="configuration"></a>

The Web API validates an incoming request header based on the username and password configured in `appsettings.json` or Configuration at Azure App Service. The app exctracts the value through `IConfiguration` class.

[appsettings.json](https://github.com/koheikawata/api-management-sample/blob/main/src/api/BasicAuth/appsettings.json)
```
"BasicAuth": {
   "UserName": "",
   "Password": ""
}
```

## Custom attribute <a name="custom-attribute"></a>

A custom attribute `BasicAuth` is on the controller and allow only requests which attach the username and password on the request header.

[WeatherforecastController](https://github.com/koheikawata/api-management-sample/blob/main/src/api/BasicAuth/Controllers/WeatherforecastController.cs)
```
[HttpGet("RequireAuth")]
[AllowAnonymous]
[AuthorizeBasicAuth]
public ActionResult<Weatherforecast> GetBasicAuth()
{
    ...
```

The custom attribute `AuthorizeBasicAuthAttribute` class inherits `AuthorizeAttribute` and `IAuthorizationFilter`. At `OnAuthorization` method, it sets `UnauthorizedResult()` to the context result if the basic authentication validation `HttpContext.Items["BasicAuth"]` is not true.

[AuthorizeBasicAuthAttribute](https://github.com/koheikawata/api-management-sample/blob/main/src/api/BasicAuth/CustomAttributes/AuthorizeBasicAuthAttribute.cs)
```
public void OnAuthorization(AuthorizationFilterContext context)
{
    if (context.HttpContext.Items["BasicAuth"] is not true)
    {
        context.Result = new UnauthorizedResult();
        return;
    }
}
```

## Middleware <a name="middleware"></a>

In the custom middleware `BasicAuthMiddleware`, it validates the username and password attached to the request header and add true to `HttpContext.Items['BasicAuth']` so it can pass the validation result to the later step of the authorization filter.

[BasicAuthMiddleware](https://github.com/koheikawata/api-management-sample/blob/main/src/api/BasicAuth/Middlewares/BasicAuthMiddleware.cs)
```
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string usernameAndPassword = encoding.GetString(Convert.FromBase64String(authHeaderVal.Parameter));
string username = usernameAndPassword.Split(new char[] { ':' })[0];
string password = usernameAndPassword.Split(new char[] { ':' })[1];
if (username == this.configuration.GetValue<string>("BasicAuth:UserName") && password == this.configuration.GetValue<string>("BasicAuth:Password"))
{
    httpContext.Items["BasicAuth"] = true;
}
```

The validation process is during the custom middleware `BasicAuthMiddleware` because it is difficult for the custom attribute filter to extract configuration values from `IConfiguration` class. According to the Microsoft documentation [Dependency Injection](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-6.0#dependency-injection), a filter attribute cannot have dependency injection constructors.

*Filters that are implemented as attributes and added directly to controller classes or action methods cannot have constructor dependencies provided by dependency injection (DI). Constructor dependencies cannot be provided by DI because attributes must have their constructor parameters supplied where they're applied.*

## Allowanonymous attribute <a name="allowanonymous"></a>

I intentionally add `[AllowAnonymous]` to the controller because .NET built-in authentication is conducted in the middleware but the request header is supposed to have only a basic authentication information.

- Without `[AllowAnonymous]` attribute in the controller, the controller does not give the request a permission to access.
- Without `app.UseAuthentication`, the authorization process does not work. (401 Unauthorized)
- Without `app.UseAuthorization`, the middleware that supports authorization cannot deal with the authorization metadata (500 internal server error.)

**Program.cs**
```
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<BasicAuthMiddleware>();
```

# Sample request <a name="sample-request"></a>

**Powershell**
```
$basicAuthUsername = "{The user name configured in the Web API}"
$basicAuthSecret = "{The password name configured in the Web API}"
$bytes = [System.Text.Encoding]::ASCII.GetBytes($basicAuthUsername + ':' + $basicAuthSecret)
$authHeader = [Convert]::ToBase64String($bytes)
$Uri = "{App URL}/Weatherforecast/RequireAuth"
$headers = @{
  "Authorization" = 'Basic ' + $authHeader
}
Invoke-RestMethod -Uri $Uri -Method Get -Headers $headers
```

**bash**
```
$basicAuthUsername = "{The user name configured in the Web API}"
$basicAuthSecret = "{The password name configured in the Web API}"
curl {App URL}/Weatherforecast/RequireAuth \
  -H "accept: application/json" \
  -H "Authorization:Basic $(echo -n $basicAuthUsername:$basicAuthSecret | openssl base64)"
```