Cirreum.Authentication.SignedRequest
1.0.4
dotnet add package Cirreum.Authentication.SignedRequest --version 1.0.4
NuGet\Install-Package Cirreum.Authentication.SignedRequest -Version 1.0.4
<PackageReference Include="Cirreum.Authentication.SignedRequest" Version="1.0.4" />
<PackageVersion Include="Cirreum.Authentication.SignedRequest" Version="1.0.4" />
<PackageReference Include="Cirreum.Authentication.SignedRequest" />
paket add Cirreum.Authentication.SignedRequest --version 1.0.4
#r "nuget: Cirreum.Authentication.SignedRequest, 1.0.4"
#:package Cirreum.Authentication.SignedRequest@1.0.4
#addin nuget:?package=Cirreum.Authentication.SignedRequest&version=1.0.4
#tool nuget:?package=Cirreum.Authentication.SignedRequest&version=1.0.4
Cirreum Authentication - SignedRequest
RFC 9421 HTTP Message Signatures authentication for service-to-service and webhook traffic
Overview
Cirreum.Authentication.SignedRequest is the server side of the SignedRequest pillar. It verifies inbound RFC 9421 (HTTP Message Signatures) + RFC 9530 (Content-Digest) requests, and can sign outbound ones (downstream calls, webhooks). The signature primitives are the shared, dependency-free Cirreum.SignedRequest package — the same code the client SDK uses — so a request signed on one side verifies byte-identically on the other.
Signatures are HMAC-SHA256 today; the ISignedRequestAlgorithm seam admits more (e.g. Ed25519) additively.
What it does
- Verifies the RFC 9421 signature, the RFC 9530
Content-Digestbody binding, andcreated/expiresfreshness against a bounded window. - Resolves credentials dynamically — you implement
DynamicSignedRequestClientResolverover your credential store; the scheme tries each active credential (zero-downtime key rotation). - Replay protection — an optional strict-nonce posture claims each RFC 9421
noncethrough anIReplayGuard, held for exactly the credential's effective freshness window. - Signs outbound requests / webhooks via
HttpRequestMessage.SignRequestAsync(...)andHttpClient.SendSignedAsync(...). - Fails closed on every error path; constant-time signature and digest comparison; the algorithm is pinned to the credential and a minimum secret-strength floor is enforced.
Installation
dotnet add package Cirreum.Authentication.SignedRequest
Composing the scheme
SignedRequest is code-composed — there is no appsettings section. The client resolver is the sole source of signing credentials, so it is a required type parameter. Compose it inside AddAuthentication on the umbrella package:
builder.AddAuthentication(auth => {
auth.AddSignedRequest<MyCredentialResolver>(o => o.ConfigureValidation(v => {
v.RequireStrictNonce = true; // optional: single-use nonce replay protection
v.TimestampTolerance = TimeSpan.FromMinutes(2);
}));
// Strict-nonce requires a coordination backend — the host fails fast at startup without one.
auth.AddCoordination(c => c.UseInMemory()); // or .UseRedis() for multi-node
});
Your resolver returns the active signing credentials for a presenting keyid:
public sealed class MyCredentialResolver(
ICredentialRepository repo,
ISignedRequestAlgorithmResolver algorithms,
IOptions<SignatureValidationOptions> options,
ILogger<MyCredentialResolver> logger)
: DynamicSignedRequestClientResolver(algorithms, options, logger) {
protected override Task<IEnumerable<StoredSigningCredential>> LookupCredentialsAsync(
string keyId, CancellationToken ct) => repo.FindActiveByKeyIdAsync(keyId, ct);
}
Wire format (RFC 9421 / RFC 9530)
| Header | Description |
|---|---|
Signature-Input |
The covered-component list + signature parameters (created, expires, nonce, keyid, alg, tag) |
Signature |
The HMAC over the RFC 9421 signature base, as an RFC 8941 byte sequence |
Content-Digest |
RFC 9530 sha-256=:…: over the body, binding it into the signature |
The default covered set is @method, @path, @query, content-digest. The credential is identified by the keyid parameter — there are no custom X-* headers.
Signing outbound requests
// Sign a prepared HttpRequestMessage:
await request.SignRequestAsync(keyId, signingSecret);
// Or sign + send with a JSON body in one call:
var response = await client.SendSignedAsync(
HttpMethod.Post, "/v1/events", keyId, signingSecret, new { eventType = "order.placed", id });
OutboundSigningOptions controls the algorithm, covered components, signature label, expires window, and nonce (≥ 128-bit).
RFC conformance profile
Cirreum SignedRequest implements a constrained Cirreum profile of RFC 9421 and RFC 9530. The implementation intentionally supports the covered components, algorithms, digest forms, and validation behavior documented here; unsupported general RFC features are out of scope unless explicitly listed.
| Area | Supported | Not supported |
|---|---|---|
| Covered components | @method, @path, @query, HTTP fields (content-digest) |
@authority (intentionally dropped), @target-uri, @scheme, @status (response signing), @query-param, component parameters (sf / key / bs / req) |
| Algorithms | hmac-sha256 |
others are additive via ISignedRequestAlgorithm; a credential must opt into a non-default algorithm |
| Digest (RFC 9530) | Content-Digest with sha-256 |
other digest algorithms (ignored), Repr-Digest / Want-*-Digest |
| Signatures per request | exactly one | multi-signature messages are rejected |
| Structured fields (RFC 8941) | the dictionary / inner-list / string / byte-sequence / integer subset these headers use | a general RFC 8941 parser |
@path / @query are normalized to the RFC 9421 §2.2.6/§2.2.7 + RFC 3986 §6.2.2 canonical form in one shared place, so signer and verifier converge. Conformance is verified against RFC 4231 (HMAC-SHA-256) and RFC 9530 (Content-Digest) published vectors, the signature base is locked by a known-answer vector, and the wire parser is fuzz-hardened.
Security considerations
- Replay — enable
RequireStrictNoncefor true single-use protection; without it a request is replayable until the freshness window lapses. - Secret strength — secrets below 128 bits are rejected; ≥ 256-bit is recommended. Store encrypted at rest and rotate by adding a new active credential.
- Audience binding — set a credential's
Audience(the RFC 9421tag) when a secret is shared across services, so a request for one audience cannot be replayed against another. - Transport — always use HTTPS; the signature protects the covered components and body digest, not everything else.
- Abuse control — implement
ISignatureValidationEventsto observe outcomes and block repeat-failing clients before validation.
License
MIT — see LICENSE.
Cirreum Foundation Framework Layered simplicity for modern .NET
| 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
- Cirreum.AuthenticationProvider (>= 1.2.1)
- Cirreum.Coordination (>= 1.2.0)
- Cirreum.SignedRequest (>= 1.0.0)
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Cirreum.Authentication.SignedRequest:
| Package | Downloads |
|---|---|
|
Cirreum.Runtime.Authentication
App-facing umbrella for the Authentication pillar. Provides AddAuthentication() and the CirreumAuthenticationBuilder type. Transitively references all six Cirreum.Authentication.* schemes (ApiKey, SignedRequest, SessionTicket, OIDC, Entra, External) — apps install this single package to get the full Authentication track. |
GitHub repositories
This package is not used by any popular GitHub repositories.