fix(mcp): serve RFC 9728 oauth protected resource metadata at path-inserted well-known URL

This commit is contained in:
Thomas Camlong committed 2026-08-19 11:08:24 +02:00
1 parent 0eb5d72ad6
commit 9b7a796934
4 files changed
+49 -58

No files matched your search

@@ -273,7 +273,7 @@ const handler = async (req: NextRequest) => {
hint: "Authenticate with an ApiKey header or via OAuth at /.well-known/oauth-authorization-server",
},
{
"WWW-Authenticate": `Bearer resource_metadata="${baseUrl}${resourcePath}/.well-known/oauth-protected-resource"`,
"WWW-Authenticate": `Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource${resourcePath}"`,
},
);
}
@@ -0,0 +1,48 @@
import type { NextRequest } from "next/server";
import { extractBaseUrlFromHeaders } from "@homarr/common";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "*",
};
export async function GET(_req: NextRequest, props: { params: Promise<{ path: string[] }> }) {
const [kind, ...rest] = (await props.params).path;
const baseUrl = extractBaseUrlFromHeaders(_req.headers);
if (kind === "oauth-authorization-server") {
return Response.json(
{
issuer: baseUrl,
authorization_endpoint: `${baseUrl}/api/mcp/oauth/authorize`,
token_endpoint: `${baseUrl}/api/mcp/oauth/token`,
registration_endpoint: `${baseUrl}/api/mcp/oauth/register`,
response_types_supported: ["code"],
grant_types_supported: ["authorization_code"],
code_challenge_methods_supported: ["S256"],
token_endpoint_auth_methods_supported: ["none"],
scopes_supported: ["mcp:tools"],
},
{ headers: corsHeaders },
);
}
if (kind === "oauth-protected-resource") {
const resourcePath = rest.join("/");
return Response.json(
{
resource: resourcePath ? `${baseUrl}/${resourcePath}` : baseUrl,
authorization_servers: [baseUrl],
},
{ headers: corsHeaders },
);
}
return Response.json({ error: "not_found" }, { status: 404, headers: corsHeaders });
}
export async function OPTIONS() {
return new Response(null, { status: 204, headers: corsHeaders });
}
@@ -1,32 +0,0 @@
import { headers } from "next/headers";
import { extractBaseUrlFromHeaders } from "@homarr/common";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "*",
};
export async function GET() {
const baseUrl = extractBaseUrlFromHeaders(await headers());
return Response.json(
{
issuer: baseUrl,
authorization_endpoint: `${baseUrl}/api/mcp/oauth/authorize`,
token_endpoint: `${baseUrl}/api/mcp/oauth/token`,
registration_endpoint: `${baseUrl}/api/mcp/oauth/register`,
response_types_supported: ["code"],
grant_types_supported: ["authorization_code"],
code_challenge_methods_supported: ["S256"],
token_endpoint_auth_methods_supported: ["none"],
scopes_supported: ["mcp:tools"],
},
{ headers: corsHeaders },
);
}
export async function OPTIONS() {
return new Response(null, { status: 204, headers: corsHeaders });
}
@@ -1,25 +0,0 @@
import { headers } from "next/headers";
import { extractBaseUrlFromHeaders } from "@homarr/common";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "*",
};
export async function GET() {
const baseUrl = extractBaseUrlFromHeaders(await headers());
return Response.json(
{
resource: `${baseUrl}/api/mcp`,
authorization_servers: [baseUrl],
},
{ headers: corsHeaders },
);
}
export async function OPTIONS() {
return new Response(null, { status: 204, headers: corsHeaders });
}