feat(backend,nextjs): Add support for M2M JWT creation and verification#7955
feat(backend,nextjs): Add support for M2M JWT creation and verification#7955wobsoriano wants to merge 2 commits intomainfrom
Conversation
🦋 Changeset detectedLatest commit: 13609a6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types 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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
@clerk/agent-toolkit
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/dev-cli
@clerk/expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review infoConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughAdds JWT-format support for M2M tokens alongside opaque tokens: new 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/backend/src/jwt/verifyMachineJwt.ts`:
- Line 89: The JWT header's `kid` must be validated before calling
resolveKeyAndVerifyJwt to avoid using undefined keys; in both verifyM2MJwt and
verifyOAuthJwt check that decoded.header.kid is a non-empty string and
throw/return a clear error (e.g., "missing JWT kid in header") if absent,
preventing downstream cache lookups like
loadClerkJwkFromPem/loadClerkJWKFromRemote from receiving `undefined`; update
the call sites that currently pass decoded.header.kid to first validate and fail
fast with a descriptive error when the kid is missing.
In `@packages/backend/src/tokens/verify.ts`:
- Around line 264-266: The code calls decodedResult.payload.sub.startsWith(...)
which can throw if sub is undefined or not a string; update the conditional in
the verify flow (the branch that currently checks M2M_SUBJECT_PREFIX) to first
ensure typeof decodedResult.payload.sub === 'string' (or use the existing
isM2MJwt type guard from machine.ts) before calling startsWith, and only call
verifyM2MJwt(token, decodedResult, options) when that guard passes; otherwise
fall through to the non-M2M verification path.
ℹ️ Review info
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (25)
.changeset/clever-ways-raise.mdintegration/tests/machine-auth/m2m.test.tspackages/backend/src/__tests__/exports.test.tspackages/backend/src/api/__tests__/M2MTokenApi.test.tspackages/backend/src/api/endpoints/M2MTokenApi.tspackages/backend/src/api/factory.tspackages/backend/src/api/resources/M2MToken.tspackages/backend/src/api/resources/__tests__/M2MToken.test.tspackages/backend/src/errors.tspackages/backend/src/fixtures/index.tspackages/backend/src/fixtures/machine.tspackages/backend/src/internal.tspackages/backend/src/jwt/verifyMachineJwt.tspackages/backend/src/tokens/__tests__/authObjects.test.tspackages/backend/src/tokens/__tests__/machine.test.tspackages/backend/src/tokens/__tests__/verify.test.tspackages/backend/src/tokens/machine.tspackages/backend/src/tokens/request.tspackages/backend/src/tokens/verify.tspackages/express/src/__tests__/getAuth.test.tspackages/fastify/src/__tests__/getAuth.test.tspackages/nextjs/src/server/__tests__/clerkMiddleware.test.tspackages/nextjs/src/server/__tests__/getAuthDataFromRequest.test.tspackages/nextjs/src/server/data/getAuthDataFromRequest.tspackages/nextjs/src/server/protect.ts
💤 Files with no reviewable changes (1)
- packages/backend/src/tests/exports.test.ts
| decoded: Jwt, | ||
| options: JwtMachineVerifyOptions, | ||
| ): Promise<MachineTokenReturnType<M2MToken, MachineTokenVerificationError>> { | ||
| const result = await resolveKeyAndVerifyJwt(token, decoded.header.kid, options); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's read the verifyMachineJwt.ts file to understand the context
wc -l packages/backend/src/jwt/verifyMachineJwt.tsRepository: clerk/javascript
Length of output: 108
🏁 Script executed:
# Read the file with line numbers to see the context around line 89
cat -n packages/backend/src/jwt/verifyMachineJwt.tsRepository: clerk/javascript
Length of output: 4983
🏁 Script executed:
# Find the key loading functions to check how they handle kid
fd -t f -e ts -e js . packages/backend/src | xargs rg -l 'loadClerkJwkFromPem|loadClerkJWKFromRemote' | head -5Repository: clerk/javascript
Length of output: 281
🏁 Script executed:
# Let's check the keys.ts file to see how these functions handle the kid parameter
cat -n packages/backend/src/tokens/keys.ts | head -80Repository: clerk/javascript
Length of output: 2823
🏁 Script executed:
# Check if there's more in keys.ts
wc -l packages/backend/src/tokens/keys.tsRepository: clerk/javascript
Length of output: 99
🏁 Script executed:
# Get the function signatures and how they use kid
rg -n -A15 'function loadClerkJwkFromPem|function loadClerkJWKFromRemote' packages/backend/src/tokens/keys.tsRepository: clerk/javascript
Length of output: 1506
🏁 Script executed:
# Check the Jwt type definition to see if kid is optional or required
rg -n 'type Jwt\s*=|interface Jwt' --type tsRepository: clerk/javascript
Length of output: 294
🏁 Script executed:
# Check the Jwt and JwtHeader type definitions
rg -n -A10 'export interface Jwt\b|export interface JwtHeader' packages/shared/src/types/jwtv2.tsRepository: clerk/javascript
Length of output: 571
🏁 Script executed:
# Also check how verifyJwt decodes the JWT to understand if kid would be present
rg -n -B5 -A20 'decode.*kid|header\.kid' packages/backend/src/tokens/ -t tsRepository: clerk/javascript
Length of output: 42
🏁 Script executed:
# Check what decodes the JWT and sets the kid in the header
rg -n 'function verifyJwt|export.*verifyJwt' packages/backend/src/tokens/verify.ts | head -5Repository: clerk/javascript
Length of output: 42
🏁 Script executed:
# Let's see the verifyJwt implementation
cat -n packages/backend/src/tokens/verify.ts | head -100Repository: clerk/javascript
Length of output: 5467
🏁 Script executed:
# Let's check where the decoded JWT comes from in verifyM2MJwt and verifyOAuthJwt
rg -n -B10 'verifyM2MJwt|verifyOAuthJwt' packages/backend/src/tokens/verify.ts | head -40Repository: clerk/javascript
Length of output: 1202
🏁 Script executed:
# Check if there's validation of the decoded JWT before passing to verifyM2MJwt
rg -n -B15 'verifyM2MJwt\|verifyOAuthJwt' packages/backend/src/tokens/verify.ts | grep -A15 -B15 'decodeJwt\|verifyM2MJwt'Repository: clerk/javascript
Length of output: 42
🏁 Script executed:
# Let's look at the full context where verifyM2MJwt is called
sed -n '240,275p' packages/backend/src/tokens/verify.tsRepository: clerk/javascript
Length of output: 1232
🏁 Script executed:
# Check the decodeJwt function to see what it returns
rg -n -A30 'export function decodeJwt' packages/backend/src/jwt/verifyJwt.tsRepository: clerk/javascript
Length of output: 1517
🏁 Script executed:
# Continue reading decodeJwt to see how it parses and validates the header
sed -n '75,130p' packages/backend/src/jwt/verifyJwt.tsRepository: clerk/javascript
Length of output: 1699
🏁 Script executed:
# Check if there's any validation that kid must be present in JwtHeader
rg -n 'kid' packages/backend/src/jwt/verifyJwt.tsRepository: clerk/javascript
Length of output: 42
🏁 Script executed:
# Let's check if there's any validation of the header.kid before calling verifyM2MJwt or verifyOAuthJwt
sed -n '240,280p' packages/backend/src/tokens/verify.tsRepository: clerk/javascript
Length of output: 1564
🏁 Script executed:
# Check the complete flow - does decodeJwt or verifyMachineAuthToken validate kid?
sed -n '240,280p' packages/backend/src/tokens/verify.ts | cat -nRepository: clerk/javascript
Length of output: 1851
🏁 Script executed:
# Let me check if there's a schema validation or type guard that validates kid presence
rg -n 'kid\|validateJwt\|validateHeader' packages/backend/src/tokens/verify.tsRepository: clerk/javascript
Length of output: 42
🏁 Script executed:
# Check if the JWT type is enforced at runtime or if it's just TypeScript
rg -n 'satisfies Jwt' packages/backend/src/jwt/verifyJwt.ts -A2 -B2Repository: clerk/javascript
Length of output: 139
🏁 Script executed:
# Let's check what happens when kid is undefined in the cache and key loading
sed -n '52,100p' packages/backend/src/tokens/keys.tsRepository: clerk/javascript
Length of output: 1428
🏁 Script executed:
# Check the complete loadClerkJWKFromRemote to see how it uses kid
sed -n '131,180p' packages/backend/src/tokens/keys.tsRepository: clerk/javascript
Length of output: 2229
Validate kid presence in JWT header before key resolution.
The JwtHeader type declares kid: string as required, but decodeJwt does not validate its presence at runtime. If a JWT lacks a kid header, decoded.header.kid will be undefined, causing issues downstream:
loadClerkJwkFromPemwould create a cache key like"local-undefined"instead of failing earlyloadClerkJWKFromRemotewould attempt cache lookup withundefinedand throw a confusing error message referencingkid='undefined'
Add validation to ensure kid is present before calling resolveKeyAndVerifyJwt in both verifyM2MJwt (line 89) and verifyOAuthJwt (line 110).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/backend/src/jwt/verifyMachineJwt.ts` at line 89, The JWT header's
`kid` must be validated before calling resolveKeyAndVerifyJwt to avoid using
undefined keys; in both verifyM2MJwt and verifyOAuthJwt check that
decoded.header.kid is a non-empty string and throw/return a clear error (e.g.,
"missing JWT kid in header") if absent, preventing downstream cache lookups like
loadClerkJwkFromPem/loadClerkJWKFromRemote from receiving `undefined`; update
the call sites that currently pass decoded.header.kid to first validate and fail
fast with a descriptive error when the kid is missing.
| if (decodedResult.payload.sub.startsWith(M2M_SUBJECT_PREFIX)) { | ||
| return verifyM2MJwt(token, decodedResult, options); | ||
| } |
There was a problem hiding this comment.
Potential runtime error: payload.sub may be undefined.
If a malformed JWT is passed with a missing or non-string sub claim, calling startsWith() on undefined will throw a TypeError. The isM2MJwt helper in machine.ts includes a type guard (typeof data.payload.sub === 'string'), but this code path accesses payload.sub directly after decode.
🐛 Proposed fix to add type guard
- if (decodedResult.payload.sub.startsWith(M2M_SUBJECT_PREFIX)) {
+ if (typeof decodedResult.payload.sub === 'string' && decodedResult.payload.sub.startsWith(M2M_SUBJECT_PREFIX)) {
return verifyM2MJwt(token, decodedResult, options);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/backend/src/tokens/verify.ts` around lines 264 - 266, The code calls
decodedResult.payload.sub.startsWith(...) which can throw if sub is undefined or
not a string; update the conditional in the verify flow (the branch that
currently checks M2M_SUBJECT_PREFIX) to first ensure typeof
decodedResult.payload.sub === 'string' (or use the existing isM2MJwt type guard
from machine.ts) before calling startsWith, and only call verifyM2MJwt(token,
decodedResult, options) when that guard passes; otherwise fall through to the
non-M2M verification path.
Description
Cherry-picked from #7883
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change
Summary by CodeRabbit
New Features
Bug Fixes / Behavior
Tests