Repository navigation
v0.1.0
Released commit: 8def72519b264c01f9dcbe6d661e8f483fdcdb39
Added
-
Explicit RFC 9449 §4.3 #1 enforcement: the new
DPoPRequestContext.FromHeaderValuesfactory rejects requests carrying
more than oneDPoPproof — as repeated header entries or as a single
comma-folded value produced by a header-combining intermediary
(RFC 9110 §5.3) — with the newDPoPMultipleProofsException, surfaced
as aDPoP-scheme challenge witherror="invalid_dpop_proof"
(RFC 9449 §7.1) by bothAuthplaneErrors.WwwAuthenticateand the MCP
middleware. Only this rejection carries that code; the other DPoP
failures keepinvalid_token. -
Both packages now multi-target
net8.0;net10.0and embed the Authplane
package icon. -
Authplane.Conformance.Sharedtest library with[Conformance]attribute,
ConformanceTracker, andConformanceCatalogAlignmentguard so
conformance assertions are tagged and tracked against the shared catalog. -
AuthplaneAuthClient.RevokeAsync(RFC 7009 token revocation). -
IDPoPNonceStore/InMemoryDPoPNonceStorefor outbound DPoP nonce handling. -
JwksCachewith background refresh at 80% TTL, stale-cache fallback,
force-refresh onkidmiss, and lock-coordinated fetches. -
Proper SSRF hardening (
Net/IpValidation.cs,Net/Ssrf.cs) with DNS pinning,
anti-rebinding TOCTOU, cloud-metadata IP block, response-size limits, and
no-redirects. -
JwksFetchSettingsandMetadataFetchSettingsfor asymmetric outbound
fetch policy. -
IRevocationChecker+IntrospectionRevocation+failClosedflag on
AuthplaneResource. -
CONTRIBUTING.md,SECURITY.md,CHANGELOG.md(this file). -
Root README Capabilities section listing every implemented RFC, security
feature, framework integration, and observability hook. -
Per-package
docs/user-guide.mdforAuthplane.SdkandAuthplane.Mcp. -
.editorconfig,Directory.Build.props,global.json,.pinact.yaml. -
CI:
dotnet format --verify-no-changesstep, conformance catalog clone,
upload ofconformance-report.{json,md}as workflow artifact. -
Packaging: SourceLink +
.snupkgsymbol packages for downstream
debugger step-through. Wired implicitly viaPublishRepositoryUrl=true
on the bundled .NET 10 SDK SourceLink — no explicitMicrosoft.SourceLink.GitHub
package reference required.IncludeSymbols=true+
SymbolPackageFormat=snupkgproduces a.snupkgnext to each.nupkg;
the publish workflow pushes both to nuget.org.
Changed
OAuthProtectedResourceMetadata.GetDocumentUrlnow removes the trailing
slash following the host component before inserting the well-known path
suffix, per RFC 9728 §3.1. A resource configured as
https://api.example.com/mcp/previously derived (and the MCP middleware
served/advertised)/.well-known/oauth-protected-resource/mcp/; it now
derives/.well-known/oauth-protected-resource/mcp. Only the document-URL
derivation changed — the resource identifier itself is still stored,
advertised, and compared exact-string everywhere else. A percent-encoded
%2Fin the final path segment is data, not a delimiter (RFC 3986 §3.3),
and survives the trim.src/Authplane/reorganised intoOAuth/,Verifier/,Net/,DPoP/,
Resilience/, andMetadata/subfolders. The public namespace remains
Authplane; no API breaking changes.OAuthOperations.cs(460 LOC) split into focused internals under
OAuth/Internal/(OAuthHttpClient,OAuthRequestBodies,
OAuthResponseParser,OAuthErrorResponse).- All OAuth client exceptions (
AuthplaneTokenRequestException,
ConsentRequiredException, parsing exceptions,ServerError) consolidated
inErrors.cs. AuthplaneVerifier.csrenamed toAuthplaneResource.cs(matches the class
it contains).- Root
README.mdrewritten as a user-facing intro per
sdk-documentation-conventions.md. Build/test/coverage commands moved to
CONTRIBUTING.md. - Per-package READMEs rewritten as short hero pages with one quickstart and a
link to the user guide;dotnet restore/build/testcontent moved to
CONTRIBUTING.md. - Coverage thresholds raised from 60/45 (line/branch) to 80/70.
- CI now runs both
Authplane.TestsandAuthplane.Mcp.Tests; the
--filter "FullyQualifiedName!~Conformance"exclusion is removed. - Smoke scripts moved from
demo/toscripts/. manual-e2e-smoke.shregisters required scopes against the authserver
before minting a token.demo/Authplane.Mcp.Demo.csprojnow declaresIsPackable=false, matching
the hygiene flag the test and conformance projects already carry. The
demo isOutputType=Exeso it was never producing a.nupkg; this just
makes the intent explicit.- Breaking (verifier). Inbound DPoP
htmcomparison is now byte-exact
ordinal per RFC 9449 §4.3 step 11 / RFC 9110 §9.1 method-token semantics.
A proof whosehtmdiffers from the request method only in case (e.g.
htm:"post"for aPOSTrequest) is now rejected with
InvalidDPoPProofException. The previous behaviour case-folded both
sides and silently accepted such proofs.
Clients that emit lowercasedhtmmust be updated. AuthplaneMcpAuth.Optionsaccepts anInboundDPoPOptions? inboundDpop
parameter and propagates it to the underlyingAuthplaneResource. The
MCP middleware's pre-tokenWWW-Authenticatechallenge scheme now
follows the configured DPoP mode:Bearer-only when DPoP is off,
DPoP-only whenRequired=true, combinedBearer+DPoPotherwise.
Previously the challenge always advertised both schemes regardless of
whether the resource accepted DPoP.ES256DpoPSignernow implementsIDisposableand releases the
underlyingECDsaprivate key. Long-lived processes that rotate signers
no longer leak native handles.AuthplaneAuthClient.DisposeAsyncalso clears the in-memory
TokenCacheso disposal releases the access tokens it was holding.- Default
tokenTypeHintparameters now reference
OAuthConstants.TokenTypeHintAccessTokeninstead of the bare string
literal; the values are unchanged.
Deprecated
AuthplaneVerifierlegacy wrapper marked[Obsolete]; will be removed in
v0.2.0. Migrate toAuthplaneResource.
Removed
- The cosmetic
ConformanceTests.csrunner (() => Task.CompletedTaskper
case) and the misleading100 / 100 passedreport it produced. Replaced
with a realConformanceCatalogAlignmentTestsguard. - ~516 LOC of duplicated conformance plumbing across
Authplane.Testsand
Authplane.Mcp.Tests.
Fixed
- Broken references to
Authplane.sln(the file isAuthplane.slnx). demo/README.mdclaimed.NET 8 SDKwhileAuthplane.csprojtargeted
net10.0.JwksCache/MetadataCachebackground refresh used to get permanently
stuck after a single call from a cancelledCancellationToken. The
outerTask.Runreceived the caller's CT; when the CT was already
cancelled at schedule time the task enteredCanceledstate and the
finally{}that clears_backgroundRefreshnever ran. From that
point on_backgroundRefreshstayed pinned to a never-completing
Taskand no further background refresh was ever triggered for the
lifetime of the cache — degrading silently to the 24h_maxStaleAge
fallback. Task now schedules withCancellationToken.None.AuthplaneClient.FetchMetadatano longer swallows transport errors
with a barecatch { }. When every discovery URL fails for
transport reasons, the last transport exception is now attached as
theInnerExceptiononMissingMetadataEndpointException.IntrospectionRevocation.IsRevokedAsyncno longer swallows
CircuitOpenExceptionwhen configuredfailOpen: true. A tripped
circuit (AS observably unhealthy) now propagates so the
resource-levelfailClosed/failOpenpolicy can decide.
Previously the lenient I/O-error handling silently accepted any
possibly-revoked token during an AS outage.- Caller cancellation (
OperationCanceledException) now propagates
through the revocation check path instead of being translated to
fail-open accepted. AuthplaneResource.DecodeHeadernow uses
Microsoft.IdentityModel.Tokens.Base64UrlEncoder.DecodeBytes
instead of a hand-rolled+/-substitution + padding helper. No
behavioural change.WWW-Authenticatechallenges emitted fromAuthplaneMcpAuthno
longer over-advertise DPoP when the configuredAuthplaneResource
rejects DPoP-bound tokens. Previously a client that picked DPoP
from the challenge negotiated acnf.jkt-bound token and saw every
request rejected asDPoPNotSupportedException.
Added
Authplane.OAuthEndpoints(internal) — single source of truth for the
/oauth/token,/oauth/introspect,/oauth/revokeendpoint paths.Authplane.OAuthRequestBodies.BuildTokenForm(token, hint?)for the
shared introspection / revocation parameter shape.Authplane.JsonHelpers(internal) —GetStringOrNull,
GetInt64OrNull,GetBoolOrNull,GetStringArrayOrEmptyextension
methods onJsonElement, replacing inline
TryGetProperty + ValueKind + Get*()boilerplate.Authplane.Base64Url,Authplane.DPoPHashes,
Authplane.JwkThumbprint,Authplane.DPoPProofBuilder,
Authplane.DPoPDefaults(all internal) — single source of truth for
base64url encoding, the DPoPathdigest, the RFC 7638 JWK
thumbprint, the proof JWT shape, and the proof TTL / clock-skew
defaults. Previously 3–4 near-identical copies of each lived across
DPoPKeyMaterial,DPoPProvider,ES256DpoPSigner, and
AuthplaneResource.MissingMetadataEndpointExceptionoverload accepting an
InnerExceptionfor transport-failure causes.OAuthConstantsexpanded with nested static classes covering OAuth
form-body parameters, RFC 6750 / 9449 error codes, HTTP header
names, MIME types, auth scheme prefixes, RFC 8414 / 9728 well-known
paths, JOSE algorithm identifiers, JWT claim names, DPoP-proof
claim names, and JWK parameter names.
Security
- SSRF hardening on outbound HTTP (DNS pinning, IP allow-list,
cloud-metadata block, response size limit, no redirects). - DPoP outbound nonce flow now resilient to AS nonce rotation.
- Bounded the
use_dpop_nonceretry inOAuthHttpClientto a single
attempt per RFC 9449 §8. A misbehaving or hostile AS that kept
returning400 use_dpop_noncewith a freshDPoP-Nonceheader
used to cause unbounded recursion inDoTokenRequestAsync/
DoPostFormAsync, exhausting either the stack or the available
sockets before any caller saw an error. AuthplaneErrors.WwwAuthenticatenow strips CR / LF / control
characters fromerror_descriptionandrealmquoted-string
parameters before emitting the header (RFC 7230 / RFC 9110 forbid
CTLs in field values). Previously the helper backslash-escaped"
and\but passed every other byte through, so attacker-controlled
fragments oferror.Messagecontaining CR/LF could inject
continuation lines into the response and forge arbitrary headers.
The MCP middleware's separate challenge builder already enforced
this invariant; both copies of the builder now agree.- DPoP
htmproof claim is now compared byte-exact against the
request method, restoring RFC 9449 §4.3 step 11 strictness. See
Changed.
Security
- SSRF hardening on outbound HTTP (DNS pinning, IP allow-list,
cloud-metadata block, response size limit, no redirects). - DPoP outbound nonce flow now resilient to AS nonce rotation.