Skip to content

fix(backend): reject machine JWTs presented as session tokens#9168

Open
dominic-clerk wants to merge 1 commit into
mainfrom
dc-machine-jwt
Open

fix(backend): reject machine JWTs presented as session tokens#9168
dominic-clerk wants to merge 1 commit into
mainfrom
dc-machine-jwt

Conversation

@dominic-clerk

@dominic-clerk dominic-clerk commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

The cookie path (authenticateRequestWithTokenInCookie) lacked the isMachineJwt() guard the header path has, so a same-instance M2M JWT in the __session cookie passed verifyToken() and produced a signed-in state with userId = "mch_...".

  • Guard the cookie path with isMachineJwt(), mirroring the header path
  • verifyToken() now also rejects any JWT tagged with a non-session token category (cat), closing the confusion regardless of transport
  • Add regression tests for both paths

Fixes SDK-107

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • Bug Fixes
    • Machine-to-machine tokens and OAuth JWTs provided through the session cookie are now rejected instead of being treated as signed-in sessions.
    • Invalid session token categories now consistently return a signed-out authentication state.
    • Session token validation rejects unsupported token types earlier, improving authorization safety.

@changeset-bot

changeset-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b66f063

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@clerk/backend Patch
@clerk/astro Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/hono Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jul 15, 2026 12:29pm
swingset Ready Ready Preview, Comment Jul 15, 2026 12:29pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Machine-token JWTs are rejected when supplied as session cookies or verified as session tokens. Cookie authentication returns a signed-out state, token verification fails before key resolution, and tests plus a patch changeset document the behavior.

Changes

Session token security

Layer / File(s) Summary
Token category verification
packages/backend/src/tokens/verify.ts, packages/backend/src/tokens/__tests__/verify.test.ts
verifyToken rejects M2M-category JWTs before JWK resolution and reports an invalid session token category, with coverage for the early failure.
Cookie authentication rejection
packages/backend/src/tokens/request.ts, packages/backend/src/tokens/__tests__/request.test.ts, .changeset/shiny-words-lay.md
Session cookies containing machine JWTs return signed-out state with TokenTypeMismatch; request tests and release notes record the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: alexcarpenter

Sequence Diagram(s)

sequenceDiagram
  participant Request
  participant CookieAuthentication
  participant TokenVerification
  Request->>CookieAuthentication: Supply machine JWT in __session
  CookieAuthentication->>CookieAuthentication: Detect machine JWT
  CookieAuthentication-->>Request: Return signedOut with TokenTypeMismatch
  TokenVerification->>TokenVerification: Reject M2M category before key resolution
Loading

Poem

A rabbit found a token in the cookie’s leafy nest,
“Not a session!” it thumped, “This guard will do its best.”
Machine JWTs hop out, signed-out states remain,
Keys are never fetched again.
Patch released with a carrot refrain!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: rejecting machine JWTs when they are presented as session tokens.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

The cookie path (authenticateRequestWithTokenInCookie) lacked the
isMachineJwt() guard the header path has, so a same-instance M2M JWT in
the __session cookie passed verifyToken() and produced a signed-in state
with userId = "mch_...".

- Guard the cookie path with isMachineJwt(), mirroring the header path
- verifyToken() now also rejects any JWT tagged with a non-session
  token category (cat), closing the confusion regardless of transport
- Add regression tests for both paths
@dominic-clerk dominic-clerk changed the title fix(backend): reject machine JWTs presented as session tokens (AISEC-91) fix(backend): reject machine JWTs presented as session tokens Jul 15, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9168

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9168

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9168

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9168

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9168

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9168

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9168

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9168

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9168

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9168

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9168

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9168

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9168

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9168

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9168

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9168

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9168

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9168

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9168

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9168

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9168

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9168

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9168

commit: b66f063

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/backend/src/tokens/__tests__/request.test.ts (1)

1283-1307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the OAuth cookie branch as well.

This regression test covers only M2M JWTs, while the production guard handles both OAuth and M2M tokens. Add or confirm a cookie case for an OAuth JWT asserting the same signed-out TokenTypeMismatch result.

As per coding guidelines, unit tests are required for all new functionality and should cover error handling and edge cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/backend/src/tokens/__tests__/request.test.ts` around lines 1283 -
1307, The cookieToken regression coverage only exercises the M2M JWT path;
extend the request tests around the existing cookieToken test to present an
OAuth JWT in the __session cookie and assert the same signed-out result with
AuthErrorReason.TokenTypeMismatch, including the existing toAuth() expectation.
Reuse the established OAuth JWT fixture or construction helpers and preserve the
current M2M coverage.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/backend/src/tokens/request.ts`:
- Around line 634-643: Move the isMachineJwt check in the cookie authentication
flow before the handleMaybeHandshakeStatus branches, so machine JWTs in
__session return the TokenTypeMismatch signedOut result even when __client_uat
is absent. Add a regression case covering a machine JWT without __client_uat and
verify the expected signed-out response.

---

Nitpick comments:
In `@packages/backend/src/tokens/__tests__/request.test.ts`:
- Around line 1283-1307: The cookieToken regression coverage only exercises the
M2M JWT path; extend the request tests around the existing cookieToken test to
present an OAuth JWT in the __session cookie and assert the same signed-out
result with AuthErrorReason.TokenTypeMismatch, including the existing toAuth()
expectation. Reuse the established OAuth JWT fixture or construction helpers and
preserve the current M2M coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 063de93e-c94c-496a-9c69-5ab6fabc5524

📥 Commits

Reviewing files that changed from the base of the PR and between 59774c6 and b66f063.

📒 Files selected for processing (5)
  • .changeset/shiny-words-lay.md
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/__tests__/verify.test.ts
  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/verify.ts

Comment on lines +634 to +643
// Machine JWTs pass verifyToken() but must not be accepted as session tokens (mirrors header path).
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (isMachineJwt(authenticateContext.sessionTokenInCookie!)) {
return signedOut({
tokenType: TokenType.SessionToken,
authenticateContext,
reason: AuthErrorReason.TokenTypeMismatch,
message: '',
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and surrounding symbols first.
ast-grep outline packages/backend/src/tokens/request.ts --view expanded || true

echo
echo "---- relevant line ranges ----"
nl -ba packages/backend/src/tokens/request.ts | sed -n '560,700p'

echo
echo "---- search for handshake helpers / machine jwt checks ----"
rg -n "isMachineJwt|handleMaybeHandshakeStatus|TokenTypeMismatch|__client_uat|sessionTokenInCookie|signedOut" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 1189


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- packages/backend/src/tokens/request.ts (lines 600-660) ----"
sed -n '600,660p' packages/backend/src/tokens/request.ts

echo
echo "---- surrounding references ----"
rg -n "handleMaybeHandshakeStatus|isMachineJwt|sessionTokenInCookie|__client_uat|TokenTypeMismatch" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4888


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- state derivation around hasActiveClient / client UAT / session token ----"
sed -n '440,625p' packages/backend/src/tokens/request.ts

echo
echo "---- machine helpers ----"
sed -n '1,220p' packages/backend/src/tokens/machine.ts

Repository: clerk/javascript

Length of output: 14777


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- handleMaybeHandshakeStatus definition ----"
sed -n '300,380p' packages/backend/src/tokens/request.ts

echo
echo "---- call sites of handleMaybeHandshakeStatus in request.ts ----"
rg -n "function handleMaybeHandshakeStatus|return handleMaybeHandshakeStatus|handleMaybeHandshakeStatus\(" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4982


Move the machine-JWT check before the cookie handshake branches. A machine JWT in __session without __client_uat hits handleMaybeHandshakeStatus first, so it never returns the intended TokenTypeMismatch signed-out state. Add a regression case for the no-__client_uat path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/backend/src/tokens/request.ts` around lines 634 - 643, Move the
isMachineJwt check in the cookie authentication flow before the
handleMaybeHandshakeStatus branches, so machine JWTs in __session return the
TokenTypeMismatch signedOut result even when __client_uat is absent. Add a
regression case covering a machine JWT without __client_uat and verify the
expected signed-out response.

@github-actions

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-15T12:33:17.387Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 0
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on b66f063.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant