fix(auth): make 2025-03-26 backcompat mock RFC 8414 §3.3-compliant (#359)

* fix(auth): make 2025-03-26 backcompat mock RFC 8414 §3.3-compliant

Drop the routePrefix from auth/2025-03-26-oauth-metadata-backcompat. The
prefix caused createAuthServer to report issuer:'<origin>/oauth' while
serving the metadata at the root /.well-known/oauth-authorization-server —
an RFC 8414 §3.3 mismatch a conforming client must reject. The MCP server
in this scenario only mounts /mcp, so the unprefixed /authorize, /token,
/register routes do not collide (the sibling endpoint-fallback scenario
already mounts them at root).

The scenario continues to test exactly what it's meant to: 2025-03-26-spec
discovery with no PRM, OAuth metadata at the root well-known path.

* fix(auth): keep /oauth prefix in 2025-03-26 backcompat, override issuer instead

Dropping routePrefix made the metadata-advertised endpoints land on the
2025-03-26 hardcoded fallback paths, so a client that fetches the
well-known doc but ignores its body would now pass. Restore the prefix
and pin the metadata issuer to the bare origin via a lazy
metadataIssuer override so the mock is RFC 8414 §3.3-compliant without
losing the fallback-vs-metadata distinction.

---------

Co-authored-by: Paul Carleton <paulc@anthropic.com>
This commit is contained in:
Felix Weinberger 2026-06-23 12:44:29 +01:00 • committed by GitHub
commit af6489593e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 13 additions and 4 deletions

View file

@ -59,8 +59,10 @@ export interface AuthServerOptions {
* Override the `issuer` value served in the AS metadata document. Used to
* test that clients validate the metadata issuer against the issuer
* identifier used to construct the well-known URL (RFC 8414 §3.3).
* Accepts a lazy getter for callers that don't know the server URL until
* after `start()`.
*/
metadataIssuer?: string;
metadataIssuer?: string | (() => string);
tokenVerifier?: MockTokenVerifier;
onTokenRequest?: (requestData: {
scope?: string;
@ -156,7 +158,10 @@ export function createAuthServer(
});
const metadata: any = {
issuer: metadataIssuer ?? `${getAuthBaseUrl()}${routePrefix}`,
issuer:
typeof metadataIssuer === 'function'
? metadataIssuer()
: (metadataIssuer ?? `${getAuthBaseUrl()}${routePrefix}`),
authorization_endpoint: `${getAuthBaseUrl()}${authRoutes.authorization_endpoint}`,
token_endpoint: `${getAuthBaseUrl()}${authRoutes.token_endpoint}`,
...(!disableDynamicRegistration && {

View file

@ -25,8 +25,12 @@ export class Auth20250326OAuthMetadataBackcompatScenario implements Scenario {
const authApp = createAuthServer(ctx, this.checks, this.server.getUrl, {
// Disable logging since the main server will already have logging enabled
loggingEnabled: false,
// Add a prefix to auth endpoints to avoid being caught by auth fallbacks
routePrefix: '/oauth'
// Keep auth endpoints off the 2025-03-26 fallback paths so a client that
// fetches metadata but ignores the advertised endpoints still 404s.
routePrefix: '/oauth',
// Metadata is served at the root well-known path, so per RFC 8414 §3.3
// the `issuer` must be the bare origin — not `<origin>/oauth`.
metadataIssuer: () => this.server.getUrl()
});
const app = createServer(
ctx,