Auth hardening CI gaps + SDK consolidation (EMA leg 2 / scopes)#1725
Conversation
Add SEP-2468/2207/2351/2352 automated coverage, unwrap EraNegotiationFailed 401s for non-legacy era, and document the SDK consolidation path. Co-authored-by: Cursor <cursoragent@cursor.com>
|
This PR was covers two types of auth cleanup. First, I wanted to ensure that after switching to the TypeScript v2 SDK that we were appropriately using the auth capabilities of the SDK instead of rolling our own (it turns out that most of that got handled in the v2 SDK port). There were a couple of small items left that were addressed in this PR. Second, I wanted to make sure we had covered all of the auth hardening issues from the 2026-07-28 spec release (per #1527). It turns out that those were all handled either before or during the v2 SDK port also, and the only gaps (other than one small fix) were in test coverage / validation for the auth hardening issues. This PR basically gets our auth support up-to-date and clean with respect to the v2 SDK and the 2026-07-28 spec. |
|
@claude review |
|
Claude finished @BobDickinson's task in 4m 22s —— View job Claude PR Review
Reviewed all 27 files. This is a clean, well-scoped PR — mostly test/coverage hardening plus a tidy consolidation onto the v2 SDK. Nothing blocking; a few observations below, mostly around one maintainability risk. ✅ What looks good
🟡 Maintainability — string-matched SDK error mapping (
|
Keep message-regex remaps honest when SDK wording drifts; document the coupling in wire.ts and the canary tests.
|
Good catch on the mock/production drift risk. We kept the message-regex remaps (the SDK still throws plain
Short comments in |
…llowed, now SDK code rejects unsupported scopes on leg 2).
|
I validated the EMA smoke test cases (I did have to update the EMA test server config because we are now pickier about requesting unsupported scopes). EMA is working (with the bundled ToDo test server and our EMA composable test server). |
Smoke test results —
|
| Claim | Test | Result |
|---|---|---|
SEP-2468 iss accept |
accepts completeOAuthFlow with matching callback iss |
✅ |
SEP-2468 iss reject-mismatch |
rejects mismatched callback iss before storing tokens |
✅ |
SEP-2468 iss reject-missing |
rejects missing iss when AS requires authorization_response iss |
✅ |
SEP-2207 offline_access |
requests offline_access when AS scopes_supported includes it |
✅ |
| SEP-2352 AS migration | does not silently reuse issuer-A tokens when discovery resolves issuer B |
✅ |
EraNegotiationFailed → 401 unwrap (isUnauthorizedError, utils.test.ts):
returns true when EraNegotiationFailed wraps UnauthorizedError in data.cause✅returns true when UnauthorizedError is on native Error.cause✅returns true for SDK UnauthorizedError/returns false for unrelated SdkError✅
✅ EMA leg 2 / scopes lean on the v2 SDK
core/auth/ema/wire.ts (wire.test.ts):
delegates to the SDK ID-JAG helper and returns the grant✅ (now usesdiscoverAndRequestJwtAuthGrant)maps real SDK missing-token-endpoint errors (canary)✅maps real SDK invalid token-exchange responses (canary)✅rejects an empty resource identifier before calling the SDK✅throws when the SDK returns success without a jwtAuthGrant✅
core/auth/scopes.ts now re-exports the SDK's computeScopeUnion / isStrictScopeSuperset — exercised end-to-end in the browser smoke below.
✅ Live browser smoke — mid-session step-up (SEP-2350 scope union + 401 recovery)
Drove the real UI (web dev server on :6274) against the local composable OAuth server (oauth-step-up-demo, test-servers/configs/oauth-step-up-demo.json) with catalog scopes mcp tools:read only, so get_temp (requires weather:read) forces a step-up. This exercises the consolidated scopes.ts union logic and the utils.ts 401 detection on the live path.
- Connect → DCR + PKCE consent — authorize page requests
mcp,tools:read.
- Connected after OAuth —
tools/listOK, protocol handshake in the log.
get_temp→ step-up modal (SEP-2350 union) — modal listsmcp(already granted),tools:read(already granted),weather:read(new).
- Re-authorize carries the union on the wire — authorize URL =
scope=mcp tools:read weather:read.
- Step-up success toast — "Step-up authorization succeeded. Retry your action."; session kept (no disconnect).
get_tempsucceeds after step-up — "The temperature in NYC is 25 degrees C";tools/callOK.
Summary
Merge is clean, validate is green across all clients, every SEP hardening / EMA-consolidation / baseUrl claim is backed by a named passing test, and the live scope-union step-up flow works end-to-end on the merged tree.
🤖 Smoke-tested with Claude Code
|
@claude review |
|
Claude finished @cliffhall's task in 1m 45s —— View job Claude PR Review (re-review for @cliffhall)
Re-reviewed the 28 auth/spec/test files. This is in good shape — the maintainability concern from the prior round (string-matched SDK error mapping) has been addressed properly, and @cliffhall's merge-and-smoke run confirms the SEP claims against a live tree. Nothing blocking. ✅ Prior review concern is resolvedThe earlier flag was that
✅ Core auth changes
✅ Test-server
|
|
Thanks for the re-review — appreciate the thorough pass. LGTM confirmed, nothing blocking. Acknowledging the two non-blocking observations (no code changes made, agreeing they're acceptable as-is):
Both are captured here as follow-up notes rather than blockers. No changes to the diff. |
Closes #1527
Summary
issreject paths, SEP-2207offline_accesson authorize, SEP-2351 discovery URL shapes, SEP-2352 AS-migration re-authorize, plusEraNegotiationFailed→ 401 unwrap inisUnauthorizedError.discoverAndRequestJwtAuthGrant,computeScopeUnion/isStrictScopeSuperset) and document the consolidation path.baseUrlfrom shared/client tsconfigs (TS 6 deprecation).Test plan
npm run ci(validate + coverage + smoke + Storybook) passed locallyissfrom the local test ASMade with Cursor