ACYTEC.Security.Hmac.AspNetCore.Functions
1.0.0
dotnet add package ACYTEC.Security.Hmac.AspNetCore.Functions --version 1.0.0
NuGet\Install-Package ACYTEC.Security.Hmac.AspNetCore.Functions -Version 1.0.0
<PackageReference Include="ACYTEC.Security.Hmac.AspNetCore.Functions" Version="1.0.0" />
<PackageVersion Include="ACYTEC.Security.Hmac.AspNetCore.Functions" Version="1.0.0" />
<PackageReference Include="ACYTEC.Security.Hmac.AspNetCore.Functions" />
paket add ACYTEC.Security.Hmac.AspNetCore.Functions --version 1.0.0
#r "nuget: ACYTEC.Security.Hmac.AspNetCore.Functions, 1.0.0"
#:package ACYTEC.Security.Hmac.AspNetCore.Functions@1.0.0
#addin nuget:?package=ACYTEC.Security.Hmac.AspNetCore.Functions&version=1.0.0
#tool nuget:?package=ACYTEC.Security.Hmac.AspNetCore.Functions&version=1.0.0
ACYTEC.Security.Hmac.AspNetCore.Functions
Azure Functions isolated-worker adapter for
ACYTEC.Security.Hmac.AspNetCore. Adds
the same HMAC signing, JWT auth, origin validation, and AES-GCM payload
encryption to a Functions app that the base package gives an ASP.NET Core
Web API, without pulling Azure Functions Worker dependencies into the base
package for consumers who don't need them.
Why this is a separate package: Azure Functions isolated worker apps
don't have a WebApplication/IApplicationBuilder, which is what the base
package's UseACYTECHmacSecurity() requires. Even with the ASP.NET Core
integration extension (Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore)
installed, FunctionsApplication.CreateBuilder(args).Build() returns a plain
IHost, never a WebApplication. This package reuses every HttpContext
based middleware class from the base package unchanged, composed by hand
into the Functions worker's own middleware pipeline instead.
Install
dotnet add package ACYTEC.Security.Hmac.AspNetCore.Functions
Your Functions project also needs
Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore (this package
depends on it, but Functions projects typically reference it directly too;
HttpContext binding only works with it installed).
Quick start
// Program.cs
using ACYTEC.Security.Hmac.AspNetCore.Extensions; // AddACYTECHmacSecurity, from the base package
using ACYTEC.Security.Hmac.AspNetCore.Functions.Extensions; // UseACYTECHmacSecurity, from this package
var builder = FunctionsApplication.CreateBuilder(args);
builder.ConfigureFunctionsWebApplication();
builder.Services.AddACYTECHmacSecurity(builder.Configuration);
builder.UseACYTECHmacSecurity();
builder.Build().Run();
The token endpoint, POST /auth/token, is a [Function] shipped inside this
package. It's discovered automatically by the Functions build the same way
any function in a referenced project or package is, nothing to write for it.
Writing a protected function
Bind the trigger as HttpRequestData, not HttpContext, and take
FunctionContext as a second parameter. Fetch the real request from
FunctionContext.GetHttpContext() inside the body:
using ACYTEC.Security.Hmac.AspNetCore.Functions.Authorization;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
public sealed class SearchUsersFunction
{
[Function("SearchUsers")]
[RequireACYTECScope("UserApi.Search")]
public async Task Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "users/search")] HttpRequestData _,
FunctionContext functionContext)
{
var context = functionContext.GetHttpContext()!;
var query = context.Request.Query["query"].ToString();
await context.Response.WriteAsJsonAsync(new { query, results = Array.Empty<string>() });
}
}
Binding
HttpContextdirectly as the trigger parameter type comes back null. This was verified against a real running Functions host, not just assumed:ACYTECHmacFunctionsMiddlewareruns ahead of the ASP.NET Core integration's own request proxying middleware, and that combination reliably produced a nullHttpContextfor the trigger parameter, with or without a secondFunctionContextparameter. Always use theHttpRequestData+FunctionContext.GetHttpContext()pattern above for any new function.Existing functions written against
HttpRequest(classic ASP.NET Core MVC style, returningIActionResult) are unaffected and don't need to change. This was also verified live, against a real production Functions app's existing, unmodifiedHttpRequest req/Task<IActionResult>endpoints:ACYTECHmacFunctionsMiddlewareauthenticated the bearer token, validated origin, and invoked them exactly as before. It's specifically theHttpContexttype as the bound parameter that comes back null;HttpRequestandIActionResultgo through a different binding path that isn't affected.
[HttpTrigger(AuthorizationLevel.Anonymous, ...)] is correct, not a
mistake: authentication and authorization are handled by
ACYTECHmacFunctionsMiddleware (bearer token, origin, scope), not by
Functions' own key based AuthorizationLevel mechanism.
Scope requirements
Azure Functions has no fluent endpoint metadata like ASP.NET Core's
RequireAuthorization(policy => ...), since there's no WebApplication
route builder to attach one to. [RequireACYTECScope("Scope.A", "Scope.B")]
on the function method is the equivalent: the caller's token must carry at
least one of the listed scopes. Omit the attribute for a function that only
needs a valid token, no specific scope.
Enabling or disabling payload encryption
Same single switch as the base package, ACYTECHmacSecurity:EnablePayloadEncryption
(default true), bound the same way via AddACYTECHmacSecurity. Setting it
to false covers TokenFunction's response too, it returns plain
{ "accessToken": "...", "expiresAt": "..." } instead of an encrypted
envelope, not just the middleware's request/response encryption for other
functions. Useful for local testing with a tool that can't do AES-GCM, see
Postman below.
Testing with Postman
Postman's pre-request script sandbox bundles CryptoJS, which handles the
HMAC-SHA256 signing fine, but not AES-GCM, CryptoJS doesn't include GCM mode.
Set EnablePayloadEncryption to false in local.settings.json while
testing in Postman:
{
"Values": {
"ACYTECHmacSecurity:EnablePayloadEncryption": "false"
}
}
Then, environment variables baseUrl (e.g. http://localhost:7071),
tokenPath (e.g. /api/auth/token, the Functions default route prefix
api plus the fixed auth/token route), clientId, clientSecret, and
appOrigin.
Pre-request Script on the POST {{baseUrl}}{{tokenPath}} request:
const method = "POST";
const path = pm.environment.get("tokenPath");
const clientId = pm.environment.get("clientId");
const secret = pm.environment.get("clientSecret");
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = CryptoJS.lib.WordArray.random(16).toString();
const bodyHash = CryptoJS.SHA256("").toString(CryptoJS.enc.Hex);
const canonical = [method, path, clientId, timestamp, nonce, bodyHash].join("\n");
const signature = CryptoJS.HmacSHA256(canonical, secret).toString(CryptoJS.enc.Base64);
pm.request.headers.upsert({ key: "X-Client-Id", value: clientId });
pm.request.headers.upsert({ key: "X-Timestamp", value: timestamp });
pm.request.headers.upsert({ key: "X-Nonce", value: nonce });
pm.request.headers.upsert({ key: "X-Signature", value: signature });
Tests script, saves the token:
pm.test("Status is 200", () => pm.response.to.have.status(200));
pm.environment.set("accessToken", pm.response.json().accessToken);
Protected function calls: Auth tab → Bearer Token → {{accessToken}}, plus
header X-App-Origin: {{appOrigin}}. No signing needed there.
Troubleshooting invalid_signature: tokenPath must include the
leading /, i.e. /api/auth/token, not api/auth/token. The server signs
against HttpContext.Request.Path, which always starts with /; a missing
leading slash silently produces a different canonical string and a
different signature. console.log(JSON.stringify(canonical)) right after
building it is the fastest way to spot this or any other stray character.
Behavior differences from the ASP.NET Core host
ASP.NET Core (ACYTEC.Security.Hmac.AspNetCore) |
Functions (this package) | |
|---|---|---|
| Token endpoint route | Configurable, ACYTECHmacSecurity:TokenEndpointPath |
Fixed at auth/token. Azure Functions HTTP trigger routes are compile time attribute values, not runtime configuration. |
| Per-endpoint scope requirement | .RequireAuthorization(policy => policy.RequireClaim("scope", "...")) |
[RequireACYTECScope("...")] |
| Trigger parameter type | N/A | HttpRequestData + FunctionContext, see above, not HttpContext directly |
Everything else, the HMAC signing scheme, token shape, origin validation,
and AES-GCM payload envelope, is byte-for-byte identical, see PROTOCOL.md
in the base package's repository.
Testing notes
The pipeline integration in ACYTECHmacFunctionsMiddleware depends on
Microsoft.Azure.Functions.Worker.FunctionContext, an abstract SDK type
that isn't practical to fully mock. The reflection-based scope resolution
and token-path matching are covered by unit tests in this package's test
project; the full pipeline (token issuance, bearer auth, origin validation,
scope enforcement, and payload encryption, all together) was verified by
actually running func start against samples/SampleFunctionsHostApi and
driving it with real signed HTTP requests, not by a mocked test. If you
change ACYTECHmacFunctionsMiddleware, re-verify against a real running
Functions host, a mocked unit test would not have caught the
HttpContext binding issue documented above.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- ACYTEC.Security.Hmac.AspNetCore (>= 1.0.0)
- Microsoft.Azure.Functions.Worker (>= 2.52.0)
- Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore (>= 2.1.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 33 | 9/1/2026 |