fix(oauth): serve AS metadata at the RFC 8414 strict path
All checks were successful
Deploy to Production / deploy (push) Successful in 1m24s
All checks were successful
Deploy to Production / deploy (push) Successful in 1m24s
Root cause of Claude Desktop's repeated "Registrierung beim Anmeldedienst fehlgeschlagen" reference ofid_897eda676d452435: RFC 8414 §3 constructs the well-known discovery URL by INSERTING "/.well-known/oauth-authorization-server" between the host and the issuer path. For issuer https://api.buildmymcpserver.com/oauth the correct location is https://api.buildmymcpserver.com/.well-known/oauth-authorization-server/oauth We previously served only the issuer-appended form (/oauth/.well-known/...), which is the historically common but RFC-incorrect placement. Claude Desktop's MCP SDK is strict per RFC 8414, hit the 404, and bailed out of discovery before ever reaching /oauth/register — so the DCR fix from earlier never had a chance to run. Now serves the same metadata at four paths via a single handler: - /.well-known/oauth-authorization-server/oauth (RFC 8414 strict) - /.well-known/oauth-authorization-server (root fallback) - /oauth/.well-known/oauth-authorization-server (historical) - /.well-known/openid-configuration (OIDC fallback) A single buildAsMetadata() helper keeps them in sync.
This commit is contained in:
parent
d2b19a5439
commit
86cf89ef42
@ -54,10 +54,31 @@ async function resolveServerByResource(resource: string) {
|
||||
}
|
||||
|
||||
export async function oauthRoutes(app: FastifyInstance): Promise<void> {
|
||||
// Authorization Server Metadata (RFC 8414) — control-plane wide
|
||||
app.get('/oauth/.well-known/oauth-authorization-server', async (_req, reply) => {
|
||||
const base = `${config.CONTROL_PLANE_PUBLIC_URL}`;
|
||||
return reply.send({
|
||||
// Authorization Server Metadata (RFC 8414).
|
||||
//
|
||||
// Our issuer is `${CONTROL_PLANE_PUBLIC_URL}/oauth`. RFC 8414 §3 says the
|
||||
// discovery URL is constructed by inserting "/.well-known/oauth-
|
||||
// authorization-server" *between the host and the issuer path*, NOT by
|
||||
// appending it after the path. So for issuer `https://api.example.com/oauth`
|
||||
// the canonical location is `https://api.example.com/.well-known/oauth-
|
||||
// authorization-server/oauth`.
|
||||
//
|
||||
// Claude Desktop's MCP SDK follows that strict construction. We previously
|
||||
// only served the issuer-appended path (`/oauth/.well-known/...`), which
|
||||
// is the historically-common but incorrect form, so Claude Desktop 404'd
|
||||
// during discovery and reported "Registrierung beim Anmeldedienst
|
||||
// fehlgeschlagen" without ever reaching the registration endpoint. We
|
||||
// now serve every realistic variant pointing at the same metadata:
|
||||
//
|
||||
// - `/.well-known/oauth-authorization-server/oauth` — RFC 8414 strict
|
||||
// - `/.well-known/oauth-authorization-server` — many clients try root
|
||||
// - `/oauth/.well-known/oauth-authorization-server` — historical/Okta-style
|
||||
// - `/.well-known/openid-configuration` — OIDC fallback
|
||||
//
|
||||
// The single source of truth is buildAsMetadata() so they cannot drift.
|
||||
const buildAsMetadata = () => {
|
||||
const base = config.CONTROL_PLANE_PUBLIC_URL;
|
||||
return {
|
||||
issuer: `${base}/oauth`,
|
||||
authorization_endpoint: `${base}/oauth/authorize`,
|
||||
token_endpoint: `${base}/oauth/token`,
|
||||
@ -72,8 +93,15 @@ export async function oauthRoutes(app: FastifyInstance): Promise<void> {
|
||||
'none',
|
||||
],
|
||||
scopes_supported: ['mcp:read', 'mcp:write'],
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
const asMetadataHandler = async (_req: unknown, reply: { send: (body: unknown) => unknown }) =>
|
||||
reply.send(buildAsMetadata());
|
||||
|
||||
app.get('/.well-known/oauth-authorization-server/oauth', asMetadataHandler);
|
||||
app.get('/.well-known/oauth-authorization-server', asMetadataHandler);
|
||||
app.get('/oauth/.well-known/oauth-authorization-server', asMetadataHandler);
|
||||
app.get('/.well-known/openid-configuration', asMetadataHandler);
|
||||
|
||||
app.get('/oauth/jwks', async (_req, reply) => {
|
||||
reply.header('cache-control', 'public, max-age=300');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user