From 55821f9e8d085bbceaab61795888e5c7f9008641 Mon Sep 17 00:00:00 2001 From: Tofik Hasanov Date: Fri, 29 May 2026 13:23:56 -0400 Subject: [PATCH] docs(auth): explain why MCP OAuth secrets/tokens aren't app-encrypted Replaces the breaking attempt to set `storeClientSecret: 'encrypted'` (cubic P1 on PR #2956) with a warning comment. That option would have broken the Gram OAuth flow: better-auth verifies every confidential client through the same decrypt/hash path, including config `trustedClients` whose secret is the plaintext GRAM_OAUTH_CLIENT_SECRET, so verification would fail (`invalid_client`). There is also nothing to encrypt: the Gram client is config-only and DCR is disabled, so no client secrets are persisted to `oauth_application`. The `accessToken`/`refreshToken` in `oauth_access_token` are generated and looked up by raw value by better-auth, so they can't be hashed/encrypted at our layer without breaking token validation; they rely on DB encryption-at-rest + short TTLs. Net: no safe app-level fix exists; documented to prevent re-introduction. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/api/src/auth/auth.server.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/api/src/auth/auth.server.ts b/apps/api/src/auth/auth.server.ts index 83f6d1bd6..51d084f83 100644 --- a/apps/api/src/auth/auth.server.ts +++ b/apps/api/src/auth/auth.server.ts @@ -536,6 +536,19 @@ export const auth = betterAuth({ oidcConfig: { loginPage: mcpLoginPage, allowDynamicClientRegistration: false, + // OAuth secrets at rest — DO NOT enable better-auth's + // `storeClientSecret: 'encrypted' | 'hashed'`. better-auth verifies every + // confidential client (including config `trustedClients`) through the same + // decrypt/hash path, but a trusted client's secret is the *plaintext* + // GRAM_OAUTH_CLIENT_SECRET from config — decrypting/hashing it would fail + // verification and break the Gram token exchange. There is also nothing to + // protect in `oauth_application`: the Gram client is config-only and DCR is + // disabled, so no client secrets are persisted to the DB. + // + // `accessToken`/`refreshToken` in `oauth_access_token` are generated and + // looked up by raw value by better-auth's oidcProvider, so they can't be + // hashed/encrypted at our layer without breaking validation; they rely on + // database encryption-at-rest and short access-token TTLs. ...(gramMcpClient ? { trustedClients: [gramMcpClient] } : {}), }, }),