Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/forward-access-token-in-require-auth.md
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 1 addition & 6 deletions packages/express/src/createServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export interface SeamlessAuthUser {
phone: string;
iat?: number;
exp?: number;
token?: string;
}

function buildProxyQueryString(queryInput: Request["query"]): string {
Expand Down Expand Up @@ -395,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",
Expand Down
1 change: 1 addition & 0 deletions packages/express/src/middleware/requireAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export function requireAuth(opts: RequireAuthOptions) {
phone: payload.phone,
iat: payload.iat,
exp: payload.exp,
token: payload.token,
};

req.user = user;
Expand Down
31 changes: 31 additions & 0 deletions packages/express/tests/requireAuth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Loading