andresmeza.com

Dev Stuff

Language / Idioma
Category Collection

Protecting dotnet 6 APIs with a shared key for internal authentication (the elegant way)

Protecting APIs is a critical task in modern software development. Whether it is an internal system or an external one, the data that is being shared must be protected. One way to protect the APIs is by using shared keys, which allow for internal authentication between systems. In this article, we will look at how to protect dotnet 6 APIs with a shared key using the provided code.

The code provided in the article is a C# class called PreSharedKeyAuthenticationHandler, which inherits from the AuthenticationHandler class. The purpose of this class is to authenticate the request based on a pre-shared key that is passed as a header in the request.

public class PreSharedKeyAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    private const string PreSharedKeyHeader = "X-PreSharedKey";
    private readonly string _preSharedKey;

    public PreSharedKeyAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
        _preSharedKey = Environment.GetEnvironmentVariable("API_SHARED_KEY")  ?? "";
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue(PreSharedKeyHeader, out var preSharedKeyValues))
        {
            return Task.FromResult(AuthenticateResult.Fail("Pre-shared key header is missing."));
        }

        var preSharedKey = preSharedKeyValues.ToString();

        if (preSharedKey != _preSharedKey)
        {
            return Task.FromResult(AuthenticateResult.Fail("Invalid pre-shared key."));
        }

        var identity = new ClaimsIdentity("PreSharedKeyAuthentication");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "PreSharedKeyAuthentication");

        return Task.FromResult(AuthenticateResult.Success(ticket));
    }
}

The first step in protecting APIs with a shared key is to generate a shared key. This key should be a complex string that is difficult to guess. Once the key is generated, it should be stored securely, such as in an environment variable. In the code provided, the shared key is retrieved from the environment variable “API_SHARED_KEY”. If the environment variable is not set, an empty string is used as the shared key.

Once the shared key is generated and stored, the next step is to create the PreSharedKeyAuthenticationHandler class. This class is responsible for handling the authentication of the request. The constructor of this class takes in several parameters, including the options for the authentication scheme, a logger factory, an encoder, and a system clock. The most important parameter is the options parameter, which provides the configuration for the authentication scheme.

The HandleAuthenticateAsync method is the heart of the PreSharedKeyAuthenticationHandler class. This method is called when the authentication handler is invoked. The first thing the method does is to check if the pre-shared key header is present in the request. If the header is missing, an authentication failure result is returned.

If the pre-shared key header is present, the code compares the header value with the shared key value that was retrieved earlier from the environment variable. If the two values match, an authentication success result is returned. If the two values do not match, an authentication failure result is returned.

If the authentication is successful, a ClaimsIdentity is created with the name “PreSharedKeyAuthentication”. A ClaimsPrincipal is then created with the identity, and an AuthenticationTicket is created with the principal and the name “PreSharedKeyAuthentication”. Finally, an authentication success result is returned with the authentication ticket.

Remember to generate the shared key and store it securely, and register the PreSharedKeyAuthenticationHandler class in your application’s authentication scheme as follows:

In your Program.cs add the following :

// Configure pre-shared key authentication
builder.Services.AddAuthentication("PreSharedKeyAuthentication")
    .AddScheme<AuthenticationSchemeOptions, PreSharedKeyAuthenticationHandler>("PreSharedKeyAuthentication", null);

To use the authentication scheme in your controller, you can decorate your methods or the controller class with the [Authorize] attribute and specify the authentication scheme to use as “PreSharedKeyAuthentication”. Here is an example controller:

[ApiController]
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = "PreSharedKeyAuthentication")]
public class MyProtectedController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("This is a protected endpoint.");
}
}

To test the authentication, you can use a tool like Curl to make a request with the proper shared key header. Here is an example curl command:

curl -H “X-PreSharedKey: YOUR_SHARED_KEY” http://localhost:5000/api/myprotected

Replace “YOUR_SHARED_KEY” with the actual shared key value. If the key is correct, the response should be “This is a protected endpoint.” If the key is incorrect or missing, the response will be an authentication error.

In conclusion, protecting your APIs with shared keys is an important step in securing your application. By using the PreSharedKeyAuthenticationHandler class provided in this article, you can easily implement shared key authentication in your dotnet 6 APIs which is more simple for internal scenarios.