From e57022b5a8becbf3c044f7987e5153b0a7d9d0fb Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Thu, 8 Oct 2026 10:47:05 -0400 Subject: [PATCH] test(verify): refuse the sign-in flow cookie presented as the session cookie Every adapter cookie can be signed with the same secret, and the ephemeral cookie is handed out before any factor is proven, so an adapter's guard must not accept it in the access cookie's place. Express, Fastify and Go pass with fells-code/seamless-auth-server#211. --- verify/CONFORMANCE.md | 5 ++-- .../harness/adapter/protectedEndpoint.spec.ts | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/verify/CONFORMANCE.md b/verify/CONFORMANCE.md index 3f2eed4..8f58a2b 100644 --- a/verify/CONFORMANCE.md +++ b/verify/CONFORMANCE.md @@ -75,8 +75,9 @@ the `Authorization` header, and no cookies are set. The access token is accepted the pair, and a used refresh token answers `401` after the reuse window. Logout ends the session. -**Guard.** `/api/me` answers `401` with no session or an altered access cookie, and the -signed-in user's id with a valid one. +**Guard.** `/api/me` answers `401` with no session, an altered access cookie, or the +sign-in flow's ephemeral cookie presented as the access cookie, and the signed-in user's +id with a valid one. **Errors.** An API validation failure keeps its `400` and body (`error: "invalid_request"` with `details.issues`). No session answers `401` with an `error` diff --git a/verify/harness/adapter/protectedEndpoint.spec.ts b/verify/harness/adapter/protectedEndpoint.spec.ts index ceae886..af034c4 100644 --- a/verify/harness/adapter/protectedEndpoint.spec.ts +++ b/verify/harness/adapter/protectedEndpoint.spec.ts @@ -1,6 +1,7 @@ import { request as playwrightRequest } from '@playwright/test'; import { cookieSignup, currentUserId } from '../lib/conformanceFlows'; +import { cookieNamed } from '../lib/cookies'; import { expect, test } from '../lib/fixtures'; // GET /api/me is the reference app's own route behind the adapter's guard, so these @@ -38,4 +39,29 @@ test.describe('protected app endpoint (adapter, cookies)', () => { await browser.dispose(); } }); + + // Every adapter cookie can be signed with the same secret, and the sign-in flow + // hands out the ephemeral cookie before any factor is proven. It must never pass + // for a session. + test('refuses the sign-in flow cookie presented as the session cookie', async ({ + adapterActor, + adapterUrl, + }) => { + await cookieSignup(adapterActor.ctx, adapterActor.email); + const login = await adapterActor.ctx.post('/auth/login', { + data: { identifier: adapterActor.email }, + }); + const ephemeral = cookieNamed(login, 'seamless-ephemeral'); + expect(ephemeral, 'login set the ephemeral cookie').toBeDefined(); + + const browser = await playwrightRequest.newContext({ + baseURL: adapterUrl, + extraHTTPHeaders: { cookie: `seamless-access=${ephemeral!.value}` }, + }); + try { + expect((await browser.get('/api/me')).status()).toBe(401); + } finally { + await browser.dispose(); + } + }); });