Quantcast
Channel: damienbod – Software Engineering
Viewing all articles
Browse latest Browse all 353

Basic Authentication in ASP.NET Core

$
0
0

This article shows how basic authentication could be implemented in an ASP.NET Core application. This is not the recommended way to implement security for user flows as the password is always sent with each request but the flow is sometimes required to implement a standard or you sometimes need to support one side of an authentication flow which requires this.

Code: https://github.com/damienbod/OAuthGrantExchangeOidcDownstreamApi

Client implementation

An client implementation needs to send an authorization header with a clientId and a clientSecret separated using the “:” char and encoded using base64. The secret is hashed using SHA256 so as not to send the original secret in the request but this does not really improve the security much, it just prevents the original application secret being shared. The request is sent using HTTPS and so the headers are encrypted.

private static string CreateBasicAuthenticationHeader(
	GetDelegatedApiTokenOAuthTokenExchangeModel reqData)
{
	var builder = new StringBuilder()
		.Append(reqData.ClientId)
		.Append(':')
		.Append(OauthTokenExchangeExtentions
			.ToSha256(reqData.ClientSecret));

	var credentials = Convert.ToBase64String(
		Encoding.ASCII.GetBytes(builder.ToString()));

	return credentials;
}

The ShA256 hash is implemented using a simple method which returns a base64 string of this.

public static string ToSha256(string text)
{
	using var sha256 = SHA256.Create();
	var bytes = Encoding.UTF8.GetBytes(text);
	var hash = sha256.ComputeHash(bytes);

	return Convert.ToBase64String(hash);
}

The credentials are sent using the Authentication header.

string credentials = CreateBasicAuthenticationHeader(reqData);

httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", credentials);

Server implementation

The server part of the flow is implemented using the idunno.Authentication.Basic Nuget package. The credentials are validated using the same hash and checked against the expected values from the configuration.

services.AddAuthentication(BasicAuthenticationDefaults.AuthenticationScheme)
.AddBasic(options =>
{
	options.Realm = "oauthtokenexchange";
	options.Events = new BasicAuthenticationEvents
	{
		OnValidateCredentials = context =>
		{
			var config = context.HttpContext.RequestServices
				.GetService<IOptions<OauthTokenExchangeConfiguration>>();
			
			if(ValidateBasicAuthentication.IsValid(context.Username, 
				context.Password, config.Value))
			{ 
				var claims = new[]
				{
					new Claim( ClaimTypes.NameIdentifier, 
						context.Username, 
						ClaimValueTypes.String, 
						context.Options.ClaimsIssuer),
					new Claim( ClaimTypes.Name, 
						context.Username, 
						ClaimValueTypes.String, 
						context.Options.ClaimsIssuer)
				};

				context.Principal = new ClaimsPrincipal(
					new ClaimsIdentity(claims, context.Scheme.Name));
				context.Success();
			}

			return Task.CompletedTask;
		}
	};
});

The ValidateBasicAuthentication class checks the used credentials.

public static class ValidateBasicAuthentication
{
    public static bool  IsValid(
		string clientId, 
		string clientSecret, 
		OauthTokenExchangeConfiguration oauthTokenExchangeConfiguration)
    {
        if(!clientId.Equals(oauthTokenExchangeConfiguration.ClientId))
        {
            return false;
        };

        if (!clientSecret.Equals(
			OauthTokenExchangeExtentions.ToSha256(
				oauthTokenExchangeConfiguration.ClientSecret)))
        {
            return false;
        };

        return true;
    }
}

The basic authentication can be validated in the authorize attribute using the correct scheme.

[Authorize(AuthenticationSchemes = BasicAuthenticationDefaults.AuthenticationScheme)]
[HttpPost("~/connect/oauthTokenExchangetoken"), Produces("application/json")]
public async Task<IActionResult> Exchange([FromForm] OauthTokenExchangePayload oauthTokenExchangePayload)
{
	// business
}

This works well but sending the password and the name on ever request is not always the best way of implementing authentication. This should only be used if required when implementing a standard. There are better ways and more secure ways of securing APIs.

Links

https://github.com/blowdart/idunno.Authentication/tree/dev/src/idunno.Authentication.Basic


Viewing all articles
Browse latest Browse all 353

Trending Articles