From 78242ddf84c41952eba6c489774330adb406880e Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Thu, 9 Jul 2026 22:13:15 -0400 Subject: [PATCH 1/3] fix(express): forward access token on req.user in requireAuth requireAuth built req.user without the inner access token, so getSeamlessUser and buildServiceAuthorization could not send a bearer credential to the auth server. Trusted server adapters received a "Missing bearer" error and every authenticated route returned 401. Attach payload.token as req.user.token, add token to the SeamlessAuthUser type, and cover it with a regression test. --- .../forward-access-token-in-require-auth.md | 9 ++++++ packages/express/src/createServer.ts | 4 +++ .../express/src/middleware/requireAuth.ts | 3 ++ packages/express/tests/requireAuth.test.js | 31 +++++++++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 .changeset/forward-access-token-in-require-auth.md diff --git a/.changeset/forward-access-token-in-require-auth.md b/.changeset/forward-access-token-in-require-auth.md new file mode 100644 index 0000000..58115ef --- /dev/null +++ b/.changeset/forward-access-token-in-require-auth.md @@ -0,0 +1,9 @@ +--- +"@seamless-auth/express": patch +--- + +Fix requireAuth dropping the access token from req.user. The middleware now +attaches the inner access token as req.user.token, so getSeamlessUser and +buildServiceAuthorization can forward it as the bearer credential when calling +the auth server. Previously trusted server adapters received a "Missing bearer" +error and every authenticated route returned 401. diff --git a/packages/express/src/createServer.ts b/packages/express/src/createServer.ts index 03980e0..8569d6e 100644 --- a/packages/express/src/createServer.ts +++ b/packages/express/src/createServer.ts @@ -85,6 +85,10 @@ export interface SeamlessAuthUser { phone: string; iat?: number; exp?: number; + // Inner access token carried from the cookie payload. Trusted server adapters + // forward this as the bearer credential when calling the auth server (e.g. + // getSeamlessUser -> buildServiceAuthorization). + token?: string; } function buildProxyQueryString(queryInput: Request["query"]): string { diff --git a/packages/express/src/middleware/requireAuth.ts b/packages/express/src/middleware/requireAuth.ts index 4ae76e1..264db7f 100644 --- a/packages/express/src/middleware/requireAuth.ts +++ b/packages/express/src/middleware/requireAuth.ts @@ -107,6 +107,9 @@ export function requireAuth(opts: RequireAuthOptions) { phone: payload.phone, iat: payload.iat, exp: payload.exp, + // Forward the inner access token so downstream helpers (getSeamlessUser -> + // buildServiceAuthorization) can authenticate service calls to the auth server. + token: payload.token, }; req.user = user; diff --git a/packages/express/tests/requireAuth.test.js b/packages/express/tests/requireAuth.test.js index 96e6ba8..75ac4a5 100644 --- a/packages/express/tests/requireAuth.test.js +++ b/packages/express/tests/requireAuth.test.js @@ -31,4 +31,35 @@ describe("requireAuth (smoke)", () => { expect(res.status).toBe(200); expect(res.body.user.sub).toBe("user-123"); }); + + it("forwards the inner access token as req.user.token", async () => { + const secret = "cookie-secret"; + const innerAccessToken = "inner-access-jwt"; + + const token = jwt.sign( + { sub: "user-123", token: innerAccessToken }, + secret, + { expiresIn: "1h" }, + ); + + const app = express(); + app.use(cookieParser()); + app.use( + requireAuth({ + cookieName: "access", + cookieSecret: secret, + }), + ); + + app.get("/protected", (req, res) => { + res.json({ user: req.user }); + }); + + const res = await request(app) + .get("/protected") + .set("Cookie", [`access=${token}`]); + + expect(res.status).toBe(200); + expect(res.body.user.token).toBe(innerAccessToken); + }); }); From 8eb810f48433201f7356d22b2480cc1f7b598e99 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Thu, 9 Jul 2026 22:19:18 -0400 Subject: [PATCH 2/3] Update requireAuth.ts --- packages/express/src/middleware/requireAuth.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/express/src/middleware/requireAuth.ts b/packages/express/src/middleware/requireAuth.ts index 264db7f..94739ed 100644 --- a/packages/express/src/middleware/requireAuth.ts +++ b/packages/express/src/middleware/requireAuth.ts @@ -107,8 +107,6 @@ export function requireAuth(opts: RequireAuthOptions) { phone: payload.phone, iat: payload.iat, exp: payload.exp, - // Forward the inner access token so downstream helpers (getSeamlessUser -> - // buildServiceAuthorization) can authenticate service calls to the auth server. token: payload.token, }; From 580dc607caf955ff2e89507cd8473ff130fdc759 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Thu, 9 Jul 2026 22:20:31 -0400 Subject: [PATCH 3/3] Update createServer.ts --- packages/express/src/createServer.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/express/src/createServer.ts b/packages/express/src/createServer.ts index 8569d6e..e004e19 100644 --- a/packages/express/src/createServer.ts +++ b/packages/express/src/createServer.ts @@ -85,9 +85,6 @@ export interface SeamlessAuthUser { phone: string; iat?: number; exp?: number; - // Inner access token carried from the cookie payload. Trusted server adapters - // forward this as the bearer credential when calling the auth server (e.g. - // getSeamlessUser -> buildServiceAuthorization). token?: string; } @@ -399,12 +396,6 @@ export function createSeamlessAuthServer( "/step-up/webauthn/finish", proxyWithIdentity("step-up/webauthn/finish", "access"), ); - - // TOTP enrollment, management, and step-up verification. All require a full - // access session and update server-side state only (no new session cookies), - // so they proxy the user's access token upstream like the step-up routes. - // TOTP-as-a-login-second-factor is intentionally not mounted here: the auth - // API does not gate login on TOTP today, so /totp/verify-login has no trigger. r.get("/totp/status", proxyWithIdentity("totp/status", "access", "GET")); r.post( "/totp/enroll/start",