- Home
- .NET tutorials
- What is Basic authentication and how to add in ASP.NET Core
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.
Authorization key, and the value is formatted with Basic, followed by a space, followed by a Base64 encoded hash of the username and password.
roundthecode and a password of K2nogspvid3ucr9nt.
roundthecode:K2nogspvid3ucr9nt
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.
- 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
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.
AuthorizeAttribute class. Within that, we can specify any authentication schemes that this attribute will use.
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.
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.
- Checking to see if there is an
Authorizationheader present in the HTTP request. - Whether the
Authorizationheader starts with the wordBasicfollowed 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.
// 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.
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.
Related tutorials