fix(idp): RFC 9207 iss param matches discovery issuer (trailing-slash normalize in createProvider) — closes #524 - #551
Merged
Conversation
…createProvider (#524) The discovery handler normalizes the `issuer` field of /.well-known/openid-configuration to the trailing-slash form, but oidc-provider was constructed with the RAW configured issuer. The provider's issuer feeds the RFC 9207 `iss` authorization-response parameter and the `iss` claim in issued tokens, so with an issuer configured slash-free: discovery issuer → http://host:port/ callback iss → http://host:port RFC 9207 requires byte-identity; strict clients (solid-oidc's handleRedirectFromLogin) rejected the callback BEFORE the token request fired and sign-in silently bounced. Reproduced on Android / nodejs-mobile (#522), where it was the final blocker to a working on-device login. Fix: normalize inside createProvider with the same expression the discovery handler uses, with bidirectional sync comments on both sites so neither normalization can drift silently. Blast-radius notes (verified before landing): - Internal verification is slash-tolerant: addTrustedIssuer and getOidcConfig strip trailing slashes; JWKS verification uses the token's own iss (self-consistent). - The discovery doc is unchanged (idp.test.js:62 keeps asserting issuer === baseUrl + '/'). - handleCredentials' programmatic-token iss stamp (credentials.js:119) is deliberately NOT touched: those tokens have their own verification path and no RFC 9207 involvement. Tests (test/idp-issuer-normalization.test.js, 3 cases): slash-free issuer gains the slash, already-slashed passes through, and the cross-component pin — provider.issuer byte-equals the discovery issuer fetched from a running server, the exact comparison strict clients perform. Full suite: 937/937 passing. Closes #524.
There was a problem hiding this comment.
Pull request overview
Fixes an IdP issuer mismatch where discovery normalizes issuer to a trailing-slash form but oidc-provider was instantiated with the raw configured issuer, causing RFC 9207 iss (auth response + token claim) to differ byte-for-byte from discovery and break strict OIDC clients.
Changes:
- Normalize the issuer inside
createProvider()to always use the trailing-slash canonical form. - Add/extend bidirectional comments to keep discovery and provider issuer normalization logic synchronized.
- Add a dedicated regression test suite that pins provider issuer normalization and asserts provider
issuerexactly equals discovery’sissuerfield.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/idp/provider.js |
Normalizes the provider’s issuer to trailing-slash form so RFC 9207 iss matches discovery. |
src/idp/index.js |
Updates discovery handler comment to explicitly sync with provider normalization and RFC 9207 requirements. |
test/idp-issuer-normalization.test.js |
Adds regression coverage for slash normalization and cross-component discovery/provider issuer equality. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
melvincarvalho
added a commit
that referenced
this pull request
Jun 11, 2026
Eight PRs merged since 0.0.206 — IdP hardening, protocol conformance, and the de-Googled-phone (#46) arc: IdP / auth - #558 passkey login degrades cleanly on stale WebViews / insecure contexts instead of crashing — drops a redundant browser crypto.randomUUID and guards both ceremonies on secure-context + WebAuthn (closes #556) - #554 IdP login-form error now survives the POST→redirect→GET cycle (rode in a non-persisted field that Interaction.save() dropped); failed sign-ins show the error instead of a silent re-render (closes #514) - #551 RFC 9207 'iss' authorization-response param normalized to match the discovery issuer, so strict OIDC clients (solid-oidc) complete sign-in (closes #524) Tunnel - #555 opt-in, per-tunnel credential passthrough (Cookie / Authorization / Set-Cookie) so authenticated access works through a tunnel; the relay's own IdP session cookies are isolated from the tunnel client (closes #530) Content negotiation / git - #553 HEAD now mirrors GET's negotiated Content-Type / Content-Length / Cache-Control for files (RFC 9110 §9.3.2 parity) (closes #552) - #550 git WAC preHandler 401/402/403 responses carry the git CORS headers, so browser git clients see the status, not a CORS error (closes #548) - #549 first HTTP-contract coverage for the git handler + fixes a DATA_ROOT test-pollution bug (closes #375) Docs / metadata - #547 README tagline + npm keywords surface the agentic positioning (closes #406)
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.
Closes #524.
Bug
The discovery handler normalizes the
issuerfield of/.well-known/openid-configurationto the trailing-slash form ("CTH compatibility",src/idp/index.js:223), butoidc-providerwas constructed with the raw configured issuer (src/idp/provider.js). The provider's issuer feeds the RFC 9207issauthorization-response parameter and theissclaim in issued tokens — so with an issuer configured slash-free:RFC 9207 requires byte-identity. Strict clients —
solid-oidc'shandleRedirectFromLogindoes exactly this comparison — rejected the callback before the token request fired, so sign-in silently bounced with no session. Reproduced on Android/nodejs-mobile (#522), where it was the final blocker to a working on-device login.Fix (1 functional line)
Normalize inside
createProviderwith the same expression the discovery handler uses:Both normalization sites now carry bidirectional sync comments naming each other and #524, so neither can drift silently.
Blast radius — verified before landing
addTrustedIssuer(solid-oidc.js:36) andgetOidcConfig(:272,288) strip trailing slashes; JWKS verification uses the token's owniss(self-consistent)idp.test.js:62keeps assertingissuer === baseUrl + '/'credentials.js:119stamps rawiss)issrisks breakage for zero benefit hereTests
New
test/idp-issuer-normalization.test.js(3 cases):provider.issuerbyte-equals theissuerfield fetched from a live server's discovery endpoint — the exact comparison strict clients perform. If either normalization drifts, this test goes red.Refs
idpIssuerpre-slashed; no longer needed)