fix(auth): callback iss must equal metadata issuer (honor metadataIssuer override) (#361)

#359 made the metadata `issuer` honor the metadataIssuer override so the
2025-03-26 backcompat scenario is RFC 8414 §3.3-compliant. The authorize
callback's `iss` parameter was still derived as `getAuthBaseUrl()+routePrefix`,
so when a scenario sets metadataIssuer (bare origin) but keeps a routePrefix,
the callback `iss` mismatches the metadata `issuer` and an RFC 9207-conforming
client correctly rejects it.

Extract `resolveIssuer()` and use it for both the metadata `issuer` and the
`'correct'`/`'normalized'` callback `iss` so they always match.
This commit is contained in:
Felix Weinberger 2026-06-24 11:30:25 +01:00 • committed by GitHub
commit 5d1b3cea3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -127,6 +127,14 @@ export function createAuthServer(
registration_endpoint: `${routePrefix}/register`
};
// The issuer identifier this AS publishes — used for both the metadata
// `issuer` field and the RFC 9207 callback `iss` parameter, which per
// RFC 9207 §2.4 must be identical under simple string comparison.
const resolveIssuer = () =>
typeof metadataIssuer === 'function'
? metadataIssuer()
: (metadataIssuer ?? `${getAuthBaseUrl()}${routePrefix}`);
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
@ -158,10 +166,7 @@ export function createAuthServer(
});
const metadata: any = {
issuer:
typeof metadataIssuer === 'function'
? metadataIssuer()
: (metadataIssuer ?? `${getAuthBaseUrl()}${routePrefix}`),
issuer: resolveIssuer(),
authorization_endpoint: `${getAuthBaseUrl()}${authRoutes.authorization_endpoint}`,
token_endpoint: `${getAuthBaseUrl()}${authRoutes.token_endpoint}`,
...(!disableDynamicRegistration && {
@ -274,16 +279,19 @@ export function createAuthServer(
redirectUrl.searchParams.set('state', state);
}
// ISS: Include iss parameter in redirect if configured
// ISS: Include iss parameter in redirect if configured. The 'correct'
// value must equal the metadata `issuer` exactly (RFC 9207 §2.4 simple
// string comparison), so honor the same metadataIssuer override the
// metadata document does.
if (issInRedirect === 'correct') {
redirectUrl.searchParams.set('iss', `${getAuthBaseUrl()}${routePrefix}`);
redirectUrl.searchParams.set('iss', resolveIssuer());
} else if (issInRedirect === 'wrong') {
redirectUrl.searchParams.set('iss', 'https://evil.example.com');
} else if (issInRedirect === 'normalized') {
// Normalization-equivalent variant of the correct issuer: identical
// after RFC 3986 scheme-based normalization (trailing slash on an
// empty path) but different under simple string comparison.
redirectUrl.searchParams.set('iss', `${getAuthBaseUrl()}${routePrefix}/`);
redirectUrl.searchParams.set('iss', `${resolveIssuer()}/`);
}
res.redirect(redirectUrl.toString());