Summary
The IdP advertises an issuer with a trailing slash in the discovery doc, but emits the RFC 9207 iss authorization-response parameter without one. RFC 9207 requires the iss value to be identical to the issuer identifier. Strict clients that compare them byte-for-byte reject the callback.
Where
src/idp/index.js (well-known handler):
// Ensure issuer has trailing slash for CTH compatibility
const normalizedIssuer = issuer.endsWith('/') ? issuer : issuer + '/';
const baseUrl = issuer.endsWith('/') ? issuer.slice(0, -1) : issuer;
...
issuer: normalizedIssuer, // discovery doc => trailing slash
But the provider is created with the raw issuer:
// src/idp/provider.js:451
const provider = new Provider(issuer, configuration);
so oidc-provider's iss param (enabled via authorization_response_iss_parameter_supported: true) is the raw issuer. When the configured issuer has no trailing slash:
- discovery
issuer → http://host:port/
- callback
iss → http://host:port
Symptom
solid-oidc (the client behind the xlogin widget) stores config.issuer from discovery and then strict-compares it to the callback iss:
// solid-oidc handleRedirectFromLogin()
if (!idp || iss !== idp) throw new Error(`Issuer mismatch: ${iss} !== ${idp}`);
(Note: its login() is slash-tolerant via trimSlash(), but handleRedirectFromLogin() is not.) The throw happens before the token request, so POST /idp/token never fires and sign-in silently bounces back to the app with no session. Reproduced embedding JSS on Android (nodejs-mobile) with idpIssuer lacking a trailing slash; tracked in #522.
Fix options
- Make the
iss param and discovery issuer identical — feed oidc-provider the same normalized (trailing-slash) issuer used in the discovery doc, or drop the discovery-only normalization so both are slash-free.
- At minimum, document that the configured issuer must already carry the canonical trailing slash.
(Workaround on the embedding side: pass idpIssuer already ending in /, which makes normalizedIssuer === issuer and aligns both. That fixed sign-in for the Android app.)
Summary
The IdP advertises an issuer with a trailing slash in the discovery doc, but emits the RFC 9207
issauthorization-response parameter without one. RFC 9207 requires theissvalue to be identical to the issuer identifier. Strict clients that compare them byte-for-byte reject the callback.Where
src/idp/index.js(well-known handler):But the provider is created with the raw issuer:
so oidc-provider's
issparam (enabled viaauthorization_response_iss_parameter_supported: true) is the raw issuer. When the configured issuer has no trailing slash:issuer→http://host:port/iss→http://host:portSymptom
solid-oidc(the client behind thexloginwidget) storesconfig.issuerfrom discovery and then strict-compares it to the callbackiss:(Note: its
login()is slash-tolerant viatrimSlash(), buthandleRedirectFromLogin()is not.) The throw happens before the token request, soPOST /idp/tokennever fires and sign-in silently bounces back to the app with no session. Reproduced embedding JSS on Android (nodejs-mobile) withidpIssuerlacking a trailing slash; tracked in #522.Fix options
issparam and discoveryissueridentical — feed oidc-provider the same normalized (trailing-slash) issuer used in the discovery doc, or drop the discovery-only normalization so both are slash-free.(Workaround on the embedding side: pass
idpIssueralready ending in/, which makesnormalizedIssuer === issuerand aligns both. That fixed sign-in for the Android app.)