Skip to content
Registration

Registration

There are two entry points. They differ in one thing: whether verification is wired into the ASP.NET Core authentication pipeline for you.

AddHmacManagerAddHmac
RegistersIHmacManagerFactorythe same, plus an authentication handler
Verificationyou call ithappens in the pipeline
Claims from schemesyou map themmapped automatically
Use whensigning only, or verifying somewhere unusualverifying an API

Both accept the same policy configuration, so moving between them is not a rewrite.

AddHmac — the usual case

Registers HmacAuthenticationHandler as an authentication scheme. A request that fails verification never reaches your endpoint.

builder.Services
    .AddAuthentication()
    .AddHmac(options =>
    {
        options.AddPolicy("MyPolicy", policy =>
        {
            policy.UsePublicKey(Guid.Parse("00000000-0000-0000-0000-000000000001"));
            policy.UsePrivateKey("zvg29s2cQ4idOqbUJWETOw==");
            policy.UseMemoryCache(maxAgeInSeconds: 300);
        });
    });

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

Any policy that verifies is a success. To require a specific policy or scheme per endpoint, see authorization.

AddHmacManager — manual control

Registers the components without touching authentication. You resolve a manager and call it yourself.

builder.Services.AddHmacManager(options =>
{
    options.AddPolicy("MyPolicy", policy =>
    {
        policy.UsePublicKey(publicKey);
        policy.UsePrivateKey(privateKey);
        policy.UseMemoryCache(maxAgeInSeconds: 300);
    });
});

IHmacManagerFactory is registered automatically, so it can be injected anywhere:

public class OrderClient(IHmacManagerFactory factory)
{
    public async Task SendAsync(HttpRequestMessage request)
    {
        var manager = factory.Create("MyPolicy")
            ?? throw new InvalidOperationException("no policy named MyPolicy");

        HmacResult result = await manager.SignAsync(request);
        // result.IsSuccess, result.Hmac
    }
}

Create returns IHmacManager? and gives back null when a name was given and does not resolve — an unregistered policy, or a scheme that policy does not declare. It does not throw, so an unhandled null is a null-reference at the call site. A second argument selects a scheme:

var manager = factory.Create("MyPolicy", "UserContext");

A blank scheme is not a failed lookup. null, "" and whitespace all mean “no scheme” — IsNullOrWhiteSpace decides it — so a value read from configuration behaves the same however absence reaches you. A real name is not trimmed, so " UserContext " is a different name and does not match.

factory.Create("MyPolicy");                  // a manager, no scheme
factory.Create("MyPolicy", null);            // the same
factory.Create("MyPolicy", "");              // the same
factory.Create("MyPolicy", "UserContext");   // a manager using that scheme
factory.Create("MyPolicy", "Typo");          // null, and logs event 1202

The TypeScript client answers each of these the same way.

Verifying by hand is the mirror image:

HmacResult result = await manager.VerifyAsync(request);
if (!result.IsSuccess) { /* reject */ }

HmacResult carries IsSuccess, the Hmac? snapshot that was computed, and DateGenerated.

Adding schemes

Either entry point:

options.AddPolicy("MyPolicy", policy =>
{
    policy.UsePublicKey(publicKey);
    policy.UsePrivateKey(privateKey);
    policy.AddScheme("UserContext", scheme =>
    {
        scheme.AddHeader("X-UserId", ClaimTypes.NameIdentifier);
        scheme.AddHeader("X-Email", ClaimTypes.Email);
    });
});

Every header a scheme names must be on the request before SignAsync is called. See schemes.

Other ways in