fix: verify MCP tokens against the MCP application's issuer

Second failure on the same path. With the aud fix in place, tokens now carry
`aud: shared-memory` correctly but are still rejected — this time on `iss`.

The MCP endpoint is a separate application in the IdP from the Web UI, and
Authentik's default per_provider issuer mode stamps each token with its own
application slug. MCP tokens therefore carry
`.../application/o/shared-memory-mcp/` while OIDC_ISSUER points at
`.../application/o/shared-memory/`, so jwtVerify throws "claim invalid: iss".

Introduce OIDC_ISSUER_MCP (defaults to OIDC_ISSUER) and use it for both the
issuer check and the JWKS URL. The protected-resource metadata now advertises
that same issuer — previously it pointed clients at the Web UI's discovery
document while the tokens came from the MCP provider.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-27 06:10:35 -07:00
co-authored by Claude Opus 5
parent 7d4e8daaaf
commit 1fed65a187
5 changed files with 47 additions and 4 deletions
@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { env } from "@/lib/env";
import { mcpIssuer } from "@/lib/auth/jwt";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
@@ -22,7 +23,10 @@ export function GET() {
return NextResponse.json({
resource,
authorization_servers: [env().OIDC_ISSUER],
// The MCP application's issuer, which is not necessarily the Web UI's —
// see mcpIssuer(). Advertising the wrong one sends clients to a discovery
// document whose tokens this endpoint will then reject on `iss`.
authorization_servers: [mcpIssuer()],
scopes_supported: ["openid", "profile", "email", audienceScope],
bearer_methods_supported: ["header"],
resource_documentation: `${resource}/`,