The OAuth path to /api/mcp has never worked end to end. Every access token arrived without an `aud` claim and jwt.ts rejected it with "claim invalid: aud" (401), even though the handshake, consent and PKCE all succeeded. Only the CLI HMAC path worked, because cli-token.ts sets the audience itself — which is why this went unnoticed. Cause: Authentik evaluates a scope mapping only when the client REQUESTS that scope by name. An MCP client learns which scopes to request from `scopes_supported` in our RFC 9728 protected-resource metadata, and we only advertised openid/profile/email. So the `aud-shared-memory` mapping was attached to the provider but never evaluated. Advertise the audience scope in that metadata. Name is derived as `aud-<OIDC_AUDIENCE>` to match the README convention, overridable with the new optional OIDC_AUDIENCE_SCOPE for deployments that named it differently. Also documents that Claude Code's RFC 8707 `resource` parameter is ignored by Authentik 2026.5, so it cannot be relied on for audience binding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
31 lines
1.1 KiB
TypeScript
31 lines
1.1 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { env } from "@/lib/env";
|
|
|
|
export const runtime = "nodejs";
|
|
export const dynamic = "force-dynamic";
|
|
|
|
/**
|
|
* RFC 9728 — OAuth 2.0 Protected Resource Metadata.
|
|
*
|
|
* MCP clients discover the authorization server (Authentik) via this
|
|
* endpoint after receiving a 401 with `WWW-Authenticate: resource_metadata=...`.
|
|
*/
|
|
export function GET() {
|
|
const resource = env().PUBLIC_URL.replace(/\/$/, "");
|
|
|
|
// The audience scope MUST be advertised. Authentik only evaluates a scope
|
|
// mapping when the client requests that scope by name, and the client only
|
|
// learns scope names from this document. Omit it and every access token
|
|
// arrives without `aud`, which jwt.ts rejects as "claim invalid: aud".
|
|
const audienceScope =
|
|
env().OIDC_AUDIENCE_SCOPE ?? `aud-${env().OIDC_AUDIENCE}`;
|
|
|
|
return NextResponse.json({
|
|
resource,
|
|
authorization_servers: [env().OIDC_ISSUER],
|
|
scopes_supported: ["openid", "profile", "email", audienceScope],
|
|
bearer_methods_supported: ["header"],
|
|
resource_documentation: `${resource}/`,
|
|
});
|
|
}
|