fix: advertise the audience scope so tokens actually carry aud
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>
This commit is contained in:
@@ -12,10 +12,18 @@ export const dynamic = "force-dynamic";
|
||||
*/
|
||||
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"],
|
||||
scopes_supported: ["openid", "profile", "email", audienceScope],
|
||||
bearer_methods_supported: ["header"],
|
||||
resource_documentation: `${resource}/`,
|
||||
});
|
||||
|
||||
@@ -18,6 +18,17 @@ const envSchema = z.object({
|
||||
OIDC_CLIENT_ID_MCP: z.string().min(1),
|
||||
OIDC_AUDIENCE: z.string().min(1),
|
||||
|
||||
// Name of the IdP scope whose mapping emits `aud: <OIDC_AUDIENCE>`.
|
||||
//
|
||||
// Most IdPs (Authentik included) only evaluate a scope mapping when the
|
||||
// client actually requests that scope. The MCP client learns which scopes
|
||||
// to request from `scopes_supported` in our protected-resource metadata,
|
||||
// so this name has to be advertised there or the mapping never runs and
|
||||
// every token arrives without an `aud` claim (-> 401 "claim invalid: aud").
|
||||
//
|
||||
// Defaults to the `aud-<audience>` convention used in the README setup.
|
||||
OIDC_AUDIENCE_SCOPE: z.string().min(1).optional(),
|
||||
|
||||
// Database
|
||||
DATABASE_URL: z.string().url(),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user