From 9486518832e6afd7d3ce5582c06669fc4d037423 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Mon, 27 Jul 2026 06:19:17 -0700 Subject: [PATCH] fix: key MCP identity on the canonical issuer, not the token issuer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verifying against OIDC_ISSUER_MCP fixed the 401, but would have introduced a quieter bug. Identity is keyed on (oidc_iss, oidc_sub) and userContextFromClaims UPSERTS rather than failing, so a token carrying the MCP application's issuer would have created a SECOND user row for the same person: MCP calls would succeed against an account holding none of their memories, and nothing would appear broken. Authentik's `sub` is `user.uid`, a user-level value that is identical across providers (verified against the live instance), so the issuer is the only differing component. Pin it to OIDC_ISSUER after verification. No stray rows exist to clean up — verification failed before this path could ever create one. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 7 +++++++ apps/web/lib/auth/jwt.ts | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c6f84a..5e2c24e 100644 --- a/README.md +++ b/README.md @@ -335,6 +335,13 @@ reliable pattern: > curl -s https://auth.example.com/application/o/shared-memory-mcp/.well-known/openid-configuration | jq .issuer > ``` > +> **Identity note:** the app verifies MCP tokens against `OIDC_ISSUER_MCP` but +> keys the user record on `OIDC_ISSUER`. Authentik's `sub` is `user.uid`, which +> is stable across providers, so the same person resolves to the same row +> whether they arrive via the Web UI or the MCP endpoint. Without that +> normalization the MCP path silently creates a second, empty account instead +> of failing visibly. +> > Note also that Claude Code sends an RFC 8707 `resource` parameter on the > authorize request; Authentik 2026.5 ignores it, so it cannot be relied on > for audience binding. The scope mapping is what sets `aud`. diff --git a/apps/web/lib/auth/jwt.ts b/apps/web/lib/auth/jwt.ts index b7bde7e..b5cd395 100644 --- a/apps/web/lib/auth/jwt.ts +++ b/apps/web/lib/auth/jwt.ts @@ -135,7 +135,24 @@ export async function authenticateBearer(authHeader: string | null): Promise