Part - 2 HMAC Authentication in ASP - NET Core Web API
Part - 2 HMAC Authentication in ASP - NET Core Web API
The HMAC stands for Hash-based Message Authentication Code. From the full form of HMAC, we
need to understand two things one is Message Authentication Code and the other one is Hash-Based.
So HMAC is a mechanism which is used for creating a Message Authentication Code by using a Hash
Function.
The most important thing that you need to keep in mind is that while generating the Message
Authentication Code using Hash Function, we need to use a Shared Secret Key. Moreover, that
Shared Secret Key must be shared between the Client and the Server involved in sending and
receiving the data.
For simplicity, let us use a dictionary to store the client IDs and their corresponding secret keys. In a
real-world application, this would be a database or a secure key management service. Let us assume
our services are going to be consumed by three different types of clients: Client1, Client2, and Client3.
So, create a class file named ClientSecrets.cs and then copy and paste the following code:
namespace HMACServerApp.Models
{
public static class ClientSecrets
{
public static Dictionary<string, string> Secrets = new Dictionary<string, string>
{
{ "ClientId1", "SecretKey1" },
{ "ClientId2", "SecretKey2" },
{ "ClientId3", "SecretKey3" },
// Add more clients as needed
};
}
}
// Timestamp tolerance to allow slight time differences between client and server
private static readonly TimeSpan TimestampTolerance = TimeSpan.FromMinutes(5);
// Constructor to initialize the middleware with the next request delegate and memory
cache
public HMACAuthenticationMiddleware(RequestDelegate next, IMemoryCache
memoryCache)
{
_next = next;
_memoryCache = memoryCache;
}
if (!isValid)
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Invalid HMAC token");
return;
}
IMemoryCache: This is used to store nonces temporarily in memory to prevent replay attacks. The
cache helps in keeping track of used nonces and their expiry.
NonceExpiry: Defines the time duration for which a nonce is considered valid. After this time, the
nonce is expired and removed from the cache.
TimestampTolerance: Specifies the allowable time difference between the client's timestamp and the
server's current time to account for slight variations in clock times. This helps in preventing replay
attacks.
Constructor: Initializes the middleware with the next request delegate and memory cache.
Invoke Method:
This is the core method that gets called for each HTTP request. It performs the following tasks:
Validates the presence and format of the Authorization header.
Extracts and validates the client ID, HMAC token, nonce, and timestamp.
Checks for nonce reuse and timestamp validity.
Reads the request body for POST and PUT methods.
Validates the HMAC token.
Calls the next middleware in the pipeline if the HMAC token is valid.
AddNonce Method: Adds a nonce to the cache to prevent reuse. Returns false if the nonce is
already used, indicating a potential replay attack.
ValidateToken Method: Constructs the request content string, including the HTTP method, path,
nonce, timestamp, and request body (if applicable). Computes the HMAC hash using the secret key
and compares it with the token provided in the request.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at
https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
}
namespace HMACServerApp.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class EmployeesController : ControllerBase
{
private static List<Employee> _employees = new List<Employee>
{
new Employee { Id = 1, Name = "Alice Smith", Position = "Manager", Salary = 75000 },
new Employee { Id = 2, Name = "Bob Johnson", Position = "Developer", Salary =
60000 },
new Employee { Id = 3, Name = "Carol White", Position = "Designer", Salary = 55000 }
};
// GET: api/Employees
[HttpGet]
public ActionResult<IEnumerable<Employee>> GetEmployees()
{
return _employees;
}
// GET: api/Employees/5
[HttpGet("{id}")]
public ActionResult<Employee> GetEmployee(int id)
{
var employee = _employees.FirstOrDefault(e => e.Id == id);
if (employee == null)
{
return NotFound();
}
return employee;
}
// POST: api/Employees
[HttpPost]
public ActionResult<Employee> PostEmployee(Employee employee)
{
employee.Id = _employees.Max(e => e.Id) + 1;
_employees.Add(employee);
// PUT: api/Employees/5
[HttpPut("{id}")]
public IActionResult PutEmployee(int id, Employee employee)
{
var existingEmployee = _employees.FirstOrDefault(e => e.Id == id);
if (existingEmployee == null)
{
return NotFound();
}
existingEmployee.Name = employee.Name;
existingEmployee.Position = employee.Position;
existingEmployee.Salary = employee.Salary;
return NoContent();
}
// DELETE: api/Employees/5
[HttpDelete("{id}")]
public IActionResult DeleteEmployee(int id)
{
var employee = _employees.FirstOrDefault(e => e.Id == id);
if (employee == null)
{
return NotFound();
}
_employees.Remove(employee);
return NoContent();
}
}
}
namespace HMACClientApp
{
public class HMACHelper
{
// Method to generate HMAC token
public static string GenerateHmacToken(string method, string path, string clientId, string
secretKey, string requestBody = "")
{
// Generate a unique nonce
var nonce = Guid.NewGuid().ToString();
// Build the request content by concatenating method, path, nonce, and timestamp
var requestContent = new StringBuilder()
.Append(method.ToUpper())
.Append(path.ToUpper())
.Append(nonce)
.Append(timestamp);
// If the HTTP method is POST or PUT, append the request body to the request content
if (method == HttpMethod.Post.Method || method == HttpMethod.Put.Method)
{
requestContent.Append(requestBody);
}
// Concatenate clientId, computedToken, nonce, and timestamp to form the final token
return $"{clientId}|{computedToken}|{nonce}|{timestamp}";
}
}
}
The above class is used for generating HMAC (Hash-based Message Authentication Code) tokens for
securing API requests in a .NET application. The HMAC token is generated based on the HTTP
method, request path, client ID, secret key, optional request body, nonce (a unique identifier), and
timestamp.
// Create an Employee
var employee = new
{
Name = "Pranaya Rout",
Position = "Developer",
Salary = 60000
};
// Update Employee
var updatedEmployee = new
{
Id = employeeId,
Name = "Rakesh Sharma",
Position = "Senior Developer",
Salary = 80000
};
requestBody = System.Text.Json.JsonSerializer.Serialize(updatedEmployee);
token = HMACHelper.GenerateHmacToken("PUT", $"/api/employees/{employeeId}",
clientId, secretKey, requestBody);
// Delete Employee
token = HMACHelper.GenerateHmacToken("DELETE", $"/api/employees/{employeeId}",
clientId, secretKey);
Console.ReadKey();
}
}
}
With the above changes in place, first run the Server application and then run the client application
and you should see it is working as expected. If everything is work as expected, then you will see the
following output:
What is Replay Attack in HMAC Authentication?
A replay attack is a form of network attack in which a valid data transmission is maliciously or
fraudulently repeated or delayed. In the context of HMAC (Hash-based Message Authentication Code)
authentication, this can occur when an attacker intercepts a legitimate request with its HMAC
signature and then resends it to the server. Since the HMAC signature is valid, if the server does not
have mechanisms to recognize that the request has been replayed, it might process the request as if
it is a new one, potentially leading to unauthorized actions.