Skip to content

Commit 565d7dd

Browse files
committed
docs: Address CodeRabbit feedback on PRD-061
- Use explicit action enum (approve/deny) instead of optional boolean for consent endpoint to prevent accidental approval on field omission - Fix tenant binding invariant: clarify slug vs UUID distinction and require resolution at each boundary
1 parent 72108a0 commit 565d7dd

1 file changed

Lines changed: 12 additions & 8 deletions

File tree

docs/prd/061-mcp-auth-session-unification.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,12 @@ and related helpers. The MCP server no longer talks to Dex.
146146
### 2. BFF / API Gateway (`services/api-gateway/`)
147147

148148
**New endpoint `POST /api/auth/mcp-consent`**: Behind full auth middleware chain (JWT
149-
validated, tenant resolved). Accepts `{mcp_state, client_id, denied?}`. On approve:
150-
generates one-time consent code, stores identity in `ConsentCodeStore`, returns
151-
`{redirect_url}` pointing to MCP callback. On deny: consumes MCP state, returns
152-
`{redirect_url}` pointing to client's redirect_uri with `error=access_denied` and the
153-
client's original state.
149+
validated, tenant resolved). Accepts `{mcp_state, client_id, action}` where `action`
150+
is an explicit enum: `"approve"` or `"deny"` (no boolean default - a missing or
151+
unrecognized action is rejected). On approve: generates one-time consent code, stores
152+
identity in `ConsentCodeStore`, returns `{redirect_url}` pointing to MCP callback.
153+
On deny: consumes MCP state, returns `{redirect_url}` pointing to client's
154+
redirect_uri with `error=access_denied` and the client's original state.
154155

155156
**New `ConsentCodeStore`**: In-memory store, same pattern as MCP's `CodeStore`. One-time
156157
consumption, 2-minute TTL, capped at 10,000 entries, background eviction.
@@ -211,8 +212,11 @@ All mandatory - no "recommended" tier. Everything listed here ships or the PRD i
211212
opaque one-time codes travel in redirects.
212213
2. **One-time code consumption**: `Consume()` must be atomic - concurrent calls for the
213214
same code return success for exactly one caller.
214-
3. **Tenant binding chain**: `MCP state.tenantSlug == BFF JWT.x-tenant-id ==
215-
consent code.tenantID` must hold end-to-end. Break at any link means rejection.
215+
3. **Tenant binding chain**: The tenant must be consistent across the entire flow.
216+
MCP state stores `tenantSlug` (from subdomain). BFF JWT contains `x-tenant-id`
217+
(UUID). The consent code stores both `tenantSlug` and `tenantID`. At each
218+
boundary, resolve slug to UUID (or vice versa) and verify they refer to the
219+
same tenant. A mismatch at any link means rejection.
216220
4. **PKCE integrity**: The outer PKCE chain (client-to-MCP) must be preserved unmodified
217221
by the consent flow.
218222
5. **Explicit consent**: BFF consent endpoint only callable via POST with Bearer auth.
@@ -284,7 +288,7 @@ The "Unverified application" badge only appears for dynamically registered clien
284288

285289
**POST `/api/auth/mcp-consent`** (BFF, requires Bearer JWT)
286290

287-
- Request: `{ mcp_state, client_id, denied?: boolean }`
291+
- Request: `{ mcp_state, client_id, action: "approve" | "deny" }`
288292
- 200 (approved): `{ redirect_url: "/oauth/callback?code=...&state=..." }`
289293
- 200 (denied): `{ redirect_url: "https://client/callback?error=access_denied&state=..." }`
290294
- 400: `{ error: "invalid_state" | "state_expired" | "client_mismatch" }`

0 commit comments

Comments
 (0)