fix(sdk): stop rewriting issuer and resource identifiers - #47
Closed
RobertoIskandarani wants to merge 1 commit into
Closed
fix(sdk): stop rewriting issuer and resource identifiers#47RobertoIskandarani wants to merge 1 commit into
RobertoIskandarani wants to merge 1 commit into
Conversation
RFC 8414 §3.3 and RFC 9728 §3.3 require the advertised issuer/resource to be identical to the configured value — a simple string comparison — and both well-known URLs are formed by inserting the well-known path segment into the identifier verbatim (RFC 8414 §3 / RFC 9728 §3). The SDK instead stripped trailing slashes in four places: - AuthplaneClient.create rewrote the configured issuer. The rewritten value became the expected `iss` at token verification, so an AS whose issuer identifier legitimately ends in "/" had every token rejected (RFC 9068 requires `iss` to carry the slash). - The PRM document URL/path helpers stripped the resource path's trailing slash, serving /mcp where RFC 9728 §3 requires /mcp/. - buildMetadataUrl stripped the issuer path's trailing slash against the RFC 8414 §3 insertion rule. - MetadataCache stripped both sides of the issuer comparison, weakening the §3.3 identical-match MUST that defeats metadata substitution. Identifiers are now validated at construction instead (absolute http(s) URL with an authority, no fragment — RFC 8707 §2) and never transformed; derivation is pure string insertion via a shared helper, since WHATWG URL serialises an empty path as "/" and cannot preserve the identifier exactly. Conformance: extends the rfc9728 well-known-path case with the trailing-slash resource datum and adds the two issuer variants (metadata issuer differing only by a trailing slash is rejected; a token whose iss matches a trailing-slash issuer verifies end to end). Migration: if a configured issuer or resource differs from the authorization server's actual identifier by a trailing slash, correct the config — the SDK no longer silently reconciles them.
RobertoIskandarani
force-pushed
the
fix/identifier-identity-trailing-slash
branch
from
July 29, 2026 14:08
8f8a77c to
3bc2afa
Compare
RobertoIskandarani
requested review from
muralx
and removed request for
a team and
muralx
July 29, 2026 14:10
Contributor
Author
|
Closing — superseded. The identifier-handling change is being reworked against the corrected RFC 8414 §3.1 / RFC 9728 §3.1 (derivation strips the terminating slash) vs §3.3 (identity preserved verbatim) scope. A replacement PR will follow. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
RFC 8414 §3.3 and RFC 9728 §3.3 require the advertised issuer/resource to be identical to the configured value ("If these values are not identical, the data contained in the response MUST NOT be used") — a simple string comparison that exists to defeat metadata substitution. Both well-known URLs are formed by inserting the well-known path segment between the authority and the identifier's path (RFC 8414 §3 / RFC 9728 §3), with no normalisation step. The SDK instead stripped trailing slashes in four places; this PR removes all four and replaces silent rewriting with construction-time validation.
The user-visible bug
AuthplaneClient.createrewrote the configured issuer (options.issuer.replace(/\/+$/g, "")), and that rewritten value became jose's expectedissuerat token verification. For an authorization server whose issuer identifier legitimately ends in/, RFC 9068 requires the token'sissto carry the trailing slash — so every token was rejected for such a deployment. Verified by reintroducing the strip: the new end-to-end conformance variant fails (discovery resolves the wrong well-known URL), and passes with the fix.The four sites
core/client.ts— issuer stored verbatim; validated, never rewritten.core/prm.ts— the PRM document URL/path helpers stripped the resource path's trailing slash, serving/mcpwhere RFC 9728 §3 requires/mcp/. Now pure insertion.core/fetching/metadataUrl.ts— pure insertion; issuerhttps://auth.example.com/tenant/now resolves to/.well-known/oauth-authorization-server/tenant/.core/fetching/documentCache.ts— issuer comparison is an exact string match on both sides; a document advertisinghttps://as.example.com/no longer passes against a configuredhttps://as.example.com. (These two strips masked the client-level bug during discovery — fixed together so the symptom is removed, not moved.)Validation instead of repair
New
core/identifiers.ts: identifiers must be absolute http(s) URLs with an authority and no fragment (RFC 8707 §2 forbids fragments). Trailing slashes, host case, and explicit ports are legal variations and preserved verbatim. Applied atAuthplaneClient.create(issuer) andAuthplaneResource(resource).Derivation is a pure string insertion via a shared split helper rather than WHATWG
URLserialisation — the WHATWG URL Standard mandates serialising an empty path as/and rewrites path bytes, either of which would break the RFC 8414 §3 / RFC 9728 §3 requirement that the derived URL preserves the identifier exactly.Conformance
rfc9728-well-known-path-must-derive-from-resource-uri— extended with the trailing-slash resource datum (/mcp/→/.well-known/oauth-protected-resource/mcp/).rfc8414-metadata-issuer-must-match-configured-issuer— variant: metadata issuer differing from the configured one only by a trailing slash is rejected (equivalent per RFC 3986 §6.2.3 is not identical).rfc9068-issuer-must-match— variant: a token whoseissis identical to a configured trailing-slash issuer verifies end to end, including discovery at the trailing-slash well-known URL (the mock AS can now serve metadata at a configurable path and advertise a suffixed issuer).Migration
If your configured issuer or resource differs from your authorization server's actual identifier by a trailing slash, correct the config — the SDK no longer silently reconciles them. (CHANGELOG entry included; the previous Unreleased entry that documented trailing-slash normalisation as intended behaviour is superseded.)
Validation
@authplane/sdk: 382/382 tests green (unit + conformance suite against the amended catalog), typecheck, biome lint, arch check. Adapter packages unaffected by the changed files.