Summary
In this article, I would share a .NET 6.0 Web API sample code that supports Basic Authentication. Here is the sample code
TOC
Concept
Configuration
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.
"BasicAuth": {
"UserName": "",
"Password": ""
}
Custom attribute
A custom attribute BasicAuth
is on the controller and allow only requests which attach the username and password on the request header.
[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.
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.Items["BasicAuth"] is not true)
{
context.Result = new UnauthorizedResult();
return;
}
}
Middleware
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.
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, 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
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
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)"
Top comments (1)
Just wanted to say a big thank you for this article and sample code. I had to move a project from .Net Core 2.1 to .Net 6.0 and the authentication code I had been using would no longer work so this helped me updated my project!