What is Basic authentication and how to add in ASP.NET Core

Published: Sunday 23 April 2023

Basic authentication is a way for a web browser to provide a username and password when making a HTTP request.

The way it works is that the username and password is added to the headers as part of the HTTP request.

C# coding challenges

C# coding challenges

Our online code editor allows you to compile the answer.

The header is added with the Authorization key, and the value is formatted with Basic, followed by a space, followed by a Base64 encoded hash of the username and password.

To generate a Base64 encoded hash, just say we have the username of roundthecode and a password of K2nogspvid3ucr9nt.

We would join the username and password into a string with a colon separating the two. As a result, it means that the username cannot have a colon it.

roundthecode:K2nogspvid3ucr9nt

The string is then encoded using Base64 and generates a hash that we can use to send as part of the HTTP request.

cm91bmR0aGVjb2RlOksybm9nc3B2aWQzdWNyOW50

Basic authentication in OAuth

Basic authentication in OAuth is used as part of the Client Credentials grant. This should only be used when there is no user present, and the client authenticates itself against the authorisation server.

It's used when requesting an access token. The client ID is used for the username, and the client secret is used for the password. This is how it works:

  • The client calls an API endpoint on the server which contains the Base64 encoded hash of the client ID and secret.
  • The server decodes the hash and authenticates the client credentials.
  • On successful authentication, an access token is generated in the form of a Bearer token. The Bearer token is then used for security across other API endpoints.
Requesting an access token in the OAuth Client Credentials grant

Requesting an access token in the OAuth Client Credentials grant

How to add Basic authentication to ASP.NET Core

When adding security functionality in a class library, a couple of NuGet packages will need to be added.

These packages contains the functionality needed to add security to an ASP.NET Core web app.

Creating an attribute

As we are only going to be using Basic authentication for an endpoint in an ASP.NET Core Web API, we want to set up an attribute. This is so we can define which endpoints require Basic authentication before they are executed.

This can be done by inheriting the AuthorizeAttribute class. Within that, we can specify any authentication schemes that this attribute will use.

Shortly, we will set up an authentication scheme for it. As a result, we will specify that we want to use the "Basic" authentication scheme as part of this attribute. The word "Basic" is stored as a constant string called AuthenticationScheme in a class called BasicAuthenticationDefaults.

// BasicAuthenticationDefaults.cs
namespace RoundTheCode.BasicAuthentication.Shared.Authentication.Basic
{
	public class BasicAuthenticationDefaults
	{
		public const string AuthenticationScheme = "Basic";
	}
}
// BasicAuthorizationAttribute.cs
using Microsoft.AspNetCore.Authorization;
using RoundTheCode.BasicAuthentication.Shared.Authentication.Basic;
 
namespace RoundTheCode.BasicAuthentication.Authentication.Basic.Attributes
{
	public class BasicAuthorizationAttribute : AuthorizeAttribute
	{
		public BasicAuthorizationAttribute()
		{
			AuthenticationSchemes = BasicAuthenticationDefaults.AuthenticationScheme;
		}
	}
}

Creating a client

When authenticating a client, we want to set up an instance that contains some details about them, including whether they are authenticated and which type they used.

This can be done by setting up a new class that inherits the IIdentity interface. It implements properties that are related to the authentication, such as the name of the client, whether they are authenticated and the type used.

// BasicAuthenticationClient.cs
using System.Security.Principal;
 
namespace RoundTheCode.BasicAuthentication.Shared.Authentication.Basic
{
	public class BasicAuthenticationClient : IIdentity
	{
		public string? AuthenticationType { get; set; }

		public bool IsAuthenticated { get; set; }

		public string? Name { get; set; }
	}
}

Handling the authentication

Next, we want to set up a handler which rules how the Basic authentication works.

First, we want to do a number of checks to see if we have everything to perform the authentication. This includes:

  • Checking to see if there is an Authorization header present in the HTTP request.
  • Whether the Authorization header starts with the word Basic followed by a space.
  • If the client ID and secret is formatted correctly when it's decoded from Base64.

Assuming all these checks pass, the next step is to see if the client ID and secret matches against the server. If they do, it creates a new instance of BasicAuthenticationClient, and ensures that that the authentication scheme is set to Basic.

It then creates a new claims prinicipal which includes any scopes requested, before returning it in the form of an authentication ticket.

// BasicAuthenticationHandler.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
 
namespace RoundTheCode.BasicAuthentication.Shared.Authentication.Basic.Handlers
{
	public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
	{
		public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
		{
		}

		protected override Task<AuthenticateResult> HandleAuthenticateAsync()
		{
			// No authorization header, so throw no result.
			if (!Request.Headers.ContainsKey("Authorization"))
			{
				return Task.FromResult(AuthenticateResult.Fail("Missing Authorization header"));
			}

			var authorizationHeader = Request.Headers["Authorization"].ToString();

			// If authorization header doesn't start with basic, throw no result.
			if (!authorizationHeader.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
			{
				return Task.FromResult(AuthenticateResult.Fail("Authorization header does not start with 'Basic'"));
			}

			// Decrypt the authorization header and split out the client id/secret which is separated by the first ':'
			var authBase64Decoded = Encoding.UTF8.GetString(Convert.FromBase64String(authorizationHeader.Replace("Basic ", "", StringComparison.OrdinalIgnoreCase)));
			var authSplit = authBase64Decoded.Split(new[] { ':' }, 2);

			// No username and password, so throw no result.
			if (authSplit.Length != 2)
			{
				return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization header format"));
			}

			// Store the client ID and secret
			var clientId = authSplit[0];
			var clientSecret = authSplit[1];

			// Client ID and secret are incorrect
			if (clientId != "roundthecode" || clientSecret != "roundthecode")
			{
				return Task.FromResult(AuthenticateResult.Fail(string.Format("The secret is incorrect for the client '{0}'", clientId)));
			}

			// Authenicate the client using basic authentication
			var client = new BasicAuthenticationClient
			{
				AuthenticationType = BasicAuthenticationDefaults.AuthenticationScheme,
				IsAuthenticated = true,
				Name = clientId
			};

			// Set the client ID as the name claim type.
			var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(client, new[]
			{
				new Claim(ClaimTypes.Name, clientId)
			}));

			// Return a success result.
			return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(claimsPrincipal, Scheme.Name)));
		}
	}
}

Configure the authentication in ASP.NET Core

With the authentication set up, it can be configured in the Program.cs file in an ASP.NET Core Web App. This is done by adding a scheme to the authentication and specifying the name of the authentication scheme alongside the handler type.

// Program.cs
...
builder.Services.AddAuthentication()
	.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>(BasicAuthenticationDefaults.AuthenticationScheme, null);
 
var app = builder.Build();
...

Use in an ASP.NET Core Web API controller

Adding the BasicAuthorization attribute can be added to an action or controller in an ASP.NET Core Web API controller.

When this is added, it uses the BasicAuthenticationHandler class to authenticate the user before executing the endpoint.

// OAuthController
using Microsoft.AspNetCore.Mvc;
using RoundTheCode.BasicAuthentication.Authentication.Basic.Attributes;
 
namespace RoundTheCode.BasicAuthentication.Controllers
{
	[Route("[controller]")]
	public class OAuthController : Controller
	{
		[HttpPost("token"), BasicAuthorization]
		public IActionResult Index()
		{
			return Ok();
		}
	}
}

In this instance, the Index action in the OAuthController class will only execute if it passes Basic authentication as it has the BasicAuthorization attribute added to it.

More information

Watch our video where we define what Basic authentication is and how to set it up in ASP.NET Core.

In-addition, download the code example used in this tutorial. It includes the attribute, handler and client used, alongside an example use in an ASP.NET Core Web API.

Security warning

Basic authentication is an insecure way of authenticating a user as a Base64 hash can easily be decoded. As a result, it means that the username and password can be exposed.

This means that it should only be used when there is no client present, and to generate an access token. From there, the access token should be used across other API endpoints.