How to implement HMAC authentication in ASP.NET Core

How to implement HMAC authentication in ASP.NET Core

HMAC is a fast and lightweight way to ensure the authenticity and data integrity of messages exchanged between web servers and clients. Learn how to use it in ASP.NET Core minimal API applications. Security is a major concern for web applications and services that use the HTTP protocol. Although HTTP is a versatile protocol that can be used in many different platforms, it is vulnerable to security threats. Enter HMAC, or Hash-based Message Authentication Code, a widely used method of securing web APIs. HMAC is a cryptographic authentication technique that uses a hash function together with a shared secret key, i.e., the key is known to both the server and the client. HMAC not only gives you authentication, but also verifies that the message hasn’t been changed in transit. Plus it is fast, lightweight, and stateless, with no secrets sent over the network and no need for token stores or session management. In this post, we’ll examine how we can work with HMAC authentication in minimal APIs in ASP.NET Core. To use the code examples provided in this article, you should have Visual Studio 2022 or Visual Studio 2026 installed in your system. If you don’t already have a copy, you can download Visual Studio 2026 here. What is HMAC authentication? How does it work? Hash-based Message Authentication Code (HMAC) is a method of creating a message authentication code that uses a hash function and a symmetric key (i.e., a key used both to encrypt and decrypt the message). In HMAC authentication, the two entities that exchange a message share the symmetric key (i.e., the key is known to both the server and the client). The symmetric key is typically generated using a cryptographic random number generator and stored in a database. HMAC provides fast, secure, stateless, lightweight services from service-to-service communications. It is used to verify that the message was received without modification (i.e., the message has integrity) and that it has originated from a trusted entity (i.e., the message has authenticity). Each message includes a unique code (the HMAC) that is generated by applying a cryptographic hash (e.g., SHA-256) to a shared secret and the message as inputs. The HMAC then can be used by the client to attach a signature to the message, thereby ensuring both confidentiality and non-repudiation of messages sent via service-to-service communication. Here is the list of the steps involved in HMAC authentication: A secret code is shared between the server and the client. The client (or service consumer) generates a cryptographic signature using the secret code. The cryptographic signature created in step 2 is attached to the authorization header of the request. The client sends the request to the server (or service provider). Upon receipt of the message, the server computes an HMAC signature using the same algorithm and shared secret that was used by the client. The server verifies that the signature generated at the server and the signature retrieved from the request header are identical. The service provider verifies that the request was received within the acceptable time frame. If the signatures match and the request was received within the acceptable time frame, then the request is treated as authorized. If the signatures don’t match or the request was not received within the acceptable time frame, the request is treated as unauthorized. When to use HMAC authentication You should use HMAC when it is an appropriate choice for internal service-to-service communication (such as microservices running on a trusted network). In this case, speed is more important than the overhead of using JSON Web Tokens (JWT) or Transport Layer Security (TLS) to authenticate requests between services. Although HMAC is a great choice for low-latency uses, it does not have built-in support for encrypting data in transit. If you need your data to be encrypted, you must use HTTPS. If you are connecting to your APIs from outside of your network, use OAuth. Implementing HMAC authentication in ASP.NET Core You can use either an authentication handler or middleware to implement HMAC authentication in ASP.NET Core. In most cases, you would use an authentication handler, but a middleware implementation may be preferable in scenarios such as complicated branches within the pipeline, the use of webhook receivers, or the presence of an authentication signature as a pre-authentication cross-cutting concern. In general, middleware should be used when there is no caller identity to validate. The following example will demonstrate how to use a middleware approach. We’ll create three projects: an ASP.NET Core Web API project (HmacDemoApi, which implements the server), a Console App project (HmacDemoApiClient, which implements the client), and a Shared Project (HmacDemoSharedLibrary, which implements a helper class that both the client and server use to compute the HMAC signature). These projects are shown in Figure 1 below. Figure 1. Solution Explorer window listing HmacDemoApi, HmacDemoApiClient, and HmacDemoSharedLibrary projects. Foundry Implement HMAC authentication middleware in ASP.NET Core Assuming that you already have created these three projects in Visual Studio, create a new file named HmacAuthenticationMiddleware.cs in the HmacDemoApi project and replace the automatically generated code with the following code. public class HmacAuthenticationMiddleware { private readonly RequestDelegate _next; public HmacAuthenticationMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { //Yet to be implemented } } The following code listing shows how you can implement the InvokeAsync method. Note how we validate the request header values, compute the HMAC signature (using the helper class in the HmacDemoSharedLibrary project, which uses theHMACSHA256 class), and then validate if the server and client signatures match. public async Task InvokeAsync(HttpContext context) { var request = context.Request; if (!request.Headers.TryGetValue("X-Api-Key", out var apiKey) || !request.Headers.TryGetValue("X-Timestamp", out var timestamp) || !request.Headers.TryGetValue("X-Signature", out var signature)) { context.Response.StatusCode = 401; return; } request.EnableBuffering(); string requestBody; using (var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true)) { requestBody = await reader.ReadToEndAsync(); request.Body.Position = 0; } var method = request.Method.ToUpperInvariant(); var path = request.Path.Value?.ToLowerInvariant() ?? ""; var payload = $"{method}\n{path}\n{timestamp}\n{requestBody}"; var computedHmac = HmacDemoHelper.ComputeHmacSha256(payload); if (!CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(computedHmac), Encoding.UTF8.GetBytes(signature!))) { context.Response.StatusCode = 401; return; } var claims = new[] { new Claim(ClaimTypes.Name, apiKey.ToString()) }; context.User = new ClaimsPrincipal(new ClaimsIdentity(claims, HmacDemoHelper.AuthenticationType)); await _next(context); } The InvokeAsync method begins by extracting the request header values. While the client will send the API key, timestamp, and the HMAC signature, the server will validate the HMAC signature using the shared secret. In our example application, we use the helper class in the HmacDemoSharedLibrary project to create the HMAC signature. As you will see in the next section, the helper class uses the HMACSHA256 class to create an HMACSHA256 instance using the secret key. Implement a shared library for HMAC authentication in ASP.NET Core The HmacDemoSharedLibrary project will implement the HmacDemoHelper class, which creates an HMAC signature using our secret key. Again, both the client and the server will use this class to compute the HMAC signature. Thus this project should be a Shared Project in Visual Studio. First, in the HmacDemoSharedLibrary project, implement the HmacDemoHelper class with the following code. public class HmacDemoHelper { private const string _secret = "SecretStringForDemonstrationPurposesOnly@123"; private const string _authenticationType = "Hmac"; public static string Secret => _secret; public static string AuthenticationType => _authenticationType; public static string ComputeHmacSha256(string payload) { byte[] secretBytes = Encoding.UTF8.GetBytes(_secret); byte[] messageBytes = Encoding.UTF8.GetBytes(payload); using var hmac = new HMACSHA256(secretBytes); byte[] hashBytes = hmac.ComputeHash(messageBytes); return Convert.ToBase64String(hashBytes); } } Next, in the HmacDemoApi project, replace the auto-generated code of the Program.cs file with the code listed below. using HmacDemoApi; var builder = WebApplication.CreateBuilder(args); // Add services to the container // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } // Enable HMAC protection across your entire API pipeline app.UseMiddleware(); app.MapPost("/api/data", (OrderRequest order, HttpContext context) => { var user = context.User.Identity?.Name; // "Client_App_01" return Results.Ok($"Product {order.ProductId} from {user}"); }); app.Run(); public record OrderRequest(int ProductId, int Quantity); Implement an HMAC authentication client in ASP.NET Core The HMAC authentication client should be a Console App project. For our example, we’ll mock a simple order service. The client builds the request by creating an order instance, serializing it, and signing the message using the shared helper class. Finally, the client invokes the API by sending the JSON data and the required authentication headers. All of this is shown in the code listing given below. using HmacDemoSharedLibrary; using System.Text; using System.Text.Json; Console.WriteLine("Press any key to invoke..."); Console.ReadLine(); await HmacClient.SendSecurePostRequestAsync(); Console.WriteLine("Press any key to stop..."); Console.ReadLine(); public class HmacClient { private static readonly HttpClient client = new HttpClient(); public static async Task SendSecurePostRequestAsync() { string url = "https://localhost:44399/api/data"; string path = "/api/data"; var order = new OrderRequest(101, 5); string jsonBody = JsonSerializer.Serialize(order); string timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); string method = "POST"; string payload = $"{method}\n{path}\n{timestamp}\n{jsonBody}"; string signature = HmacDemoHelper.ComputeHmacSha256(payload); var request = new HttpRequestMessage(HttpMethod.Post, url); request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json"); request.Headers.Add("X-Api-Key", "Client_App_01"); request.Headers.Add("X-Timestamp", timestamp); request.Headers.Add("X-Signature", signature); var response = await client.SendAsync(request); string result = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Status Code: {response.StatusCode}"); Console.WriteLine($"Server Response: {result}"); } } public record OrderRequest(int ProductId, int Quantity); HMAC authentication in action To run this application, first set the Web API (HmacDemoApi) and Console App (HmacDemoApiClient) projects as the start projects in Visual Studio. After you’ve set the Web API and the Console App as the start projects and executed the application in Visual Studio, you’ll observe that the Web API project will launch a web page and the Console App will launch a Command window. Figure 2 below shows how the client app captures the server response and displays it in the Console window. Figure 2. The response from the server indicates that HMAC authentication succeeded. Foundry Because the HMAC authentication succeeded (HMAC created by client = HMAC created by server), the server returned Status Code: OK, and the client received a response from the server accordingly. Note that the server response message “Product 101 from Client_App_Demo_01” was assembled in the Program.cs file of the HmacDemoApi (Web API) project. The server created the response by combining the JSON body of the request and the API key from the request header. HMAC takeaways Using a shared secret key and a cryptographic hash function (like SHA-256), HMAC produces a unique code for a given message, allowing you to sign your messages and authenticate who you are communicating with. This unique code or signature (also known as the HMAC) ensures that the message wasn’t altered in transit from its source to its destination and that it was created by a legitimate source. Both the client and server must build the signed string identically. When using HMAC, one should also implement replay protection using a timestamp window. A narrow timestamp window prevents replay attacks by ensuring that the same request can’t be used more than once. Because HMAC detects tampering but does not encrypt the data in transit, you should still use HTTPS when using HMAC authentication.

Original Source

Read the full article at Infoworld →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.