Skip to content

Releases: AuthPlane/cs-sdk

Release list

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 16:17

Released commit: 8bb3826c6adce78490f125676fe61fb3f84eb84c

Added

  • AccessDeniedException (access_denied, 403) and InvalidTargetException (invalid_target, 400) typed by MapOAuthError; neither counts toward the circuit breaker.
  • AuthplaneMcpAuth.Options.ResourceMetadataUrl points the resource_metadata challenge at an AS-hosted PRM document; validated at construction with the resource identifier's rules, and http://authserver:8080/... is accepted.
  • README Compatibility section: tested against authserver 0.2.0; introspection-based revocation requires authserver 0.1.2 or later.
  • Resource-server DPoP nonce enforcement (RFC 9449 §9), opt-in via InboundDPoPOptions.nonceIssuer; null, the default, leaves existing deployments unchanged. When enabled, every client's first DPoP request takes a 401 use_dpop_nonce round trip.
  • Inbound DPoP nonce API: IDPoPNonceIssuer, HmacDPoPNonceIssuer (key required; CreateEphemeral() for single-process), DPoPNonceRequiredException, AuthplaneErrors.ResponseHeaders and VerifiedClaims.NextDPoPNonce. Custom adapters must copy both onto the response.
  • AuthplaneErrors.ErrorResponseBody(...), ErrorDescriptionFor(code) and ErrorCodeFor(error) build the RFC 6750 §3 JSON error body and fixed description from the challenge's error code.
  • Authplane.Mcp — the middleware logs the exception behind every failure: Error for a 5xx, Debug for a rejection. Hosts without an ILoggerFactory get no output.

Changed

  • TokenRevokedException from an active=false introspection now names the other cause: the AS not recognising this resource server as the token's owner (authserver ≥ 0.1.2 runtime-client rule).
  • User guides document access_denied vs consent_required on token exchange, the allowed_client_ids operator step, and the confidential + runtime-client requirement for introspection.
  • manual-e2e-setup.sh no longer sets AUTHPLANE_CLIENT_CREDENTIALS_ENABLED (on by default since authserver 0.2.0) and accepts AUTHSERVER_REF to check out an authserver ref before building.
  • manual-e2e-smoke.sh no longer calls POST /admin/scopes (the route does not exist in authserver 0.2.0; the demo provisioner creates the scopes).
  • OAuthProtectedResourceMetadata.GetDocumentUrl derives the document URL from the configured identifier string verbatim, query included. Migration: an identifier with an uppercase scheme or host, a default port or dot-segments now advertises a different URL; update hard-coded expectations.
  • Breaking A resource identifier must be an absolute URL with a scheme and host, with no fragment, userinfo, whitespace, backslash, C0 control or DEL, a port of in-range digits with no leading zero, and an RFC 3986 path and query; anything else throws ArgumentException at construction. Migration: configure the full URL and percent-encode offending octets.
  • The same identifier gates run in ProtectedResourceMetadata's constructor and Build.
  • BREAKING AuthplaneErrors.WwwAuthenticate(...) now emits a fixed error_description chosen by the error= code instead of the exception message. Migration: log error.Message server-side, or pass verboseDescription: true.
  • BREAKING Authplane.Mcp — every failure answers with an RFC 6750 §3 JSON body and a fixed description per error code, instead of prose and the exception message. Migration: parse error and error_description from the body.
  • BREAKING Authplane.Mcp — a 503 (JWKS or metadata unreachable) answers temporarily_unavailable instead of server_error. Migration: match temporarily_unavailable to detect a transient outage.

Deprecated

  • VerifiedClaims.MayAct marked [Obsolete]: authserver 0.2.0 no longer issues may_act; removed in the next minor.

Fixed

  • The MCP middleware's decoded-path fallback held %5C back from decoding while Kestrel decodes it, so a %5C-bearing resource identifier answered 401 at its own advertised metadata URL on hosts without a raw request target.
  • AuthplaneResource.CreateAsync no longer abandons the AuthplaneClient it builds when the resource constructor rejects its arguments; DisposeAsync is now idempotent.
  • The authority now has an RFC 3986 §3.2.2 character-production gate, so an internationalized host is rejected at construction instead of reaching a WWW-Authenticate challenge as a non-URI.
  • An authority that is only userinfo (https://user@) is reported as a missing host with ArgumentException, instead of throwing IndexOutOfRangeException.
  • AuthplaneClient.CreateAsync no longer abandons the client it built when the priming metadata fetch fails or the caller's token is cancelled.
  • An opaque resource identifier such as mailto:ops@example.com is no longer reported as carrying userinfo; it is still refused, now because an opaque URI has no host to derive a metadata document URL from.
  • The MCP middleware answers a JWKS or metadata outage with 503 instead of 401 invalid_token, and sends WWW-Authenticate only on a 401.

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 07 Aug 15:24

Released commit: 8def72519b264c01f9dcbe6d661e8f483fdcdb39

Added

  • Explicit RFC 9449 §4.3 #1 enforcement: the new
    DPoPRequestContext.FromHeaderValues factory rejects requests carrying
    more than one DPoP proof — as repeated header entries or as a single
    comma-folded value produced by a header-combining intermediary
    (RFC 9110 §5.3) — with the new DPoPMultipleProofsException, surfaced
    as a DPoP-scheme challenge with error="invalid_dpop_proof"
    (RFC 9449 §7.1) by both AuthplaneErrors.WwwAuthenticate and the MCP
    middleware. Only this rejection carries that code; the other DPoP
    failures keep invalid_token.

  • Both packages now multi-target net8.0;net10.0 and embed the Authplane
    package icon.

  • Authplane.Conformance.Shared test library with [Conformance] attribute,
    ConformanceTracker, and ConformanceCatalogAlignment guard so
    conformance assertions are tagged and tracked against the shared catalog.

  • AuthplaneAuthClient.RevokeAsync (RFC 7009 token revocation).

  • IDPoPNonceStore / InMemoryDPoPNonceStore for outbound DPoP nonce handling.

  • JwksCache with background refresh at 80% TTL, stale-cache fallback,
    force-refresh on kid miss, 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.

  • JwksFetchSettings and MetadataFetchSettings for asymmetric outbound
    fetch policy.

  • IRevocationChecker + IntrospectionRevocation + failClosed flag 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.md for Authplane.Sdk and Authplane.Mcp.

  • .editorconfig, Directory.Build.props, global.json, .pinact.yaml.

  • CI: dotnet format --verify-no-changes step, conformance catalog clone,
    upload of conformance-report.{json,md} as workflow artifact.

  • Packaging: SourceLink + .snupkg symbol packages for downstream
    debugger step-through. Wired implicitly via PublishRepositoryUrl=true
    on the bundled .NET 10 SDK SourceLink — no explicit Microsoft.SourceLink.GitHub
    package reference required. IncludeSymbols=true +
    SymbolPackageFormat=snupkg produces a .snupkg next to each .nupkg;
    the publish workflow pushes both to nuget.org.

Changed

  • OAuthProtectedResourceMetadata.GetDocumentUrl now 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
    %2F in the final path segment is data, not a delimiter (RFC 3986 §3.3),
    and survives the trim.
  • src/Authplane/ reorganised into OAuth/, Verifier/, Net/, DPoP/,
    Resilience/, and Metadata/ 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
    in Errors.cs.
  • AuthplaneVerifier.cs renamed to AuthplaneResource.cs (matches the class
    it contains).
  • Root README.md rewritten 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/test content moved to
    CONTRIBUTING.md.
  • Coverage thresholds raised from 60/45 (line/branch) to 80/70.
  • CI now runs both Authplane.Tests and Authplane.Mcp.Tests; the
    --filter "FullyQualifiedName!~Conformance" exclusion is removed.
  • Smoke scripts moved from demo/ to scripts/.
  • manual-e2e-smoke.sh registers required scopes against the authserver
    before minting a token.
  • demo/Authplane.Mcp.Demo.csproj now declares IsPackable=false, matching
    the hygiene flag the test and conformance projects already carry. The
    demo is OutputType=Exe so it was never producing a .nupkg; this just
    makes the intent explicit.
  • Breaking (verifier). Inbound DPoP htm comparison is now byte-exact
    ordinal per RFC 9449 §4.3 step 11 / RFC 9110 §9.1 method-token semantics.
    A proof whose htm differs from the request method only in case (e.g.
    htm:"post" for a POST request) is now rejected with
    InvalidDPoPProofException. The previous behaviour case-folded both
    sides and silently accepted such proofs.
    Clients that emit lowercased htm must be updated.
  • AuthplaneMcpAuth.Options accepts an InboundDPoPOptions? inboundDpop
    parameter and propagates it to the underlying AuthplaneResource. The
    MCP middleware's pre-token WWW-Authenticate challenge scheme now
    follows the configured DPoP mode: Bearer-only when DPoP is off,
    DPoP-only when Required=true, combined Bearer+DPoP otherwise.
    Previously the challenge always advertised both schemes regardless of
    whether the resource accepted DPoP.
  • ES256DpoPSigner now implements IDisposable and releases the
    underlying ECDsa private key. Long-lived processes that rotate signers
    no longer leak native handles.
  • AuthplaneAuthClient.DisposeAsync also clears the in-memory
    TokenCache so disposal releases the access tokens it was holding.
  • Default tokenTypeHint parameters now reference
    OAuthConstants.TokenTypeHintAccessToken instead of the bare string
    literal; the values are unchanged.

Deprecated

  • AuthplaneVerifier legacy wrapper marked [Obsolete]; will be removed in
    v0.2.0. Migrate to AuthplaneResource.

Removed

  • The cosmetic ConformanceTests.cs runner (() => Task.CompletedTask per
    case) and the misleading 100 / 100 passed report it produced. Replaced
    with a real ConformanceCatalogAlignmentTests guard.
  • ~516 LOC of duplicated conformance plumbing across Authplane.Tests and
    Authplane.Mcp.Tests.

Fixed

  • Broken references to Authplane.sln (the file is Authplane.slnx).
  • demo/README.md claimed .NET 8 SDK while Authplane.csproj targeted
    net10.0.
  • JwksCache / MetadataCache background refresh used to get permanently
    stuck after a single call from a cancelled CancellationToken. The
    outer Task.Run received the caller's CT; when the CT was already
    cancelled at schedule time the task entered Canceled state and the
    finally{} that clears _backgroundRefresh never ran. From that
    point on _backgroundRefresh stayed pinned to a never-completing
    Task and no further background refresh was ever triggered for the
    lifetime of the cache — degrading silently to the 24h _maxStaleAge
    fallback. Task now schedules with CancellationToken.None.
  • AuthplaneClient.FetchMetadata no longer swallows transport errors
    with a bare catch { }. When every discovery URL fails for
    transport reasons, the last transport exception is now attached as
    the InnerException on MissingMetadataEndpointException.
  • IntrospectionRevocation.IsRevokedAsync no longer swallows
    CircuitOpenException when configured failOpen: true. A tripped
    circuit (AS observably unhealthy) now propagates so the
    resource-level failClosed / failOpen policy 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.DecodeHeader now uses
    Microsoft.IdentityModel.Tokens.Base64UrlEncoder.DecodeBytes
    instead of a hand-rolled +/- substitution + padding helper. No
    behavioural change.
  • WWW-Authenticate challenges emitted from AuthplaneMcpAuth no
    longer over-advertise DPoP when the configured AuthplaneResource
    rejects DPoP-bound tokens. Previously a client that picked DPoP
    from the challenge negotiated a cnf.jkt-bound token and saw every
    request rejected as DPoPNotSupportedException.

Added

  • Authplane.OAuthEndpoints (internal) — single source of truth for the
    /oauth/token, /oauth/introspect, /oauth/revoke endpoint paths.
  • Authplane.OAuthRequestBodies.BuildTokenForm(token, hint?) for the
    shared introspection / revocation parameter shape.
  • Authplane.JsonHelpers (internal) — GetStringOrNull,
    GetInt64OrNull, GetBoolOrNull, GetStringArrayOrEmpty extension
    methods on JsonElement, 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 DPoP ath digest, 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.
  • MissingMetadataEndpointException overload accepting an
    InnerException for transport-failure causes.
  • OAuthConstants expanded 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 na...
Read more