Skip to content

fix(backend): Return TokenVerificationError from decodeJwt for undecodable tokens - #9268

Open
wobsoriano wants to merge 1 commit into
mainfrom
rob/fix-issue-9263
Open

fix(backend): Return TokenVerificationError from decodeJwt for undecodable tokens#9268
wobsoriano wants to merge 1 commit into
mainfrom
rob/fix-issue-9263

Conversation

@wobsoriano

@wobsoriano wobsoriano commented Jul 29, 2026

Copy link
Copy Markdown
Member

Description

decodeJwt checks that a token has three dot-separated segments, but the base64url.parse and JSON.parse calls after that check were unguarded. A token like aaa.bbb.ccc threw a raw SyntaxError instead of the documented TokenVerificationError, and verifyToken rejected with the same SyntaxError.

The segment parsing is now wrapped so undecodable headers, payloads, or signatures produce a TokenVerificationError with reason token-invalid, matching the behavior for every other malformed token shape.

Fixes #9263

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:

@changeset-bot

changeset-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 293de4d

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 29, 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 29, 2026 3:59am
swingset Ready Ready Preview, Comment Jul 29, 2026 3:59am

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9268

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 293de4d

@github-actions

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-29T04:01:28.641Z

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 293de4d.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

decodeJwt now catches invalid base64url and JSON decoding failures for JWT headers, payloads, and signatures, returning TokenVerificationError with reason token-invalid. Tests cover malformed encoded segments and invalid JSON, and a Changesets entry records a patch release for @clerk/backend.

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

Suggested reviewers: jescalan

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main fix: returning TokenVerificationError for undecodable JWTs.
Description check ✅ Passed The description is directly related to the token-decoding bug and the implemented fix.
Linked Issues check ✅ Passed The changes satisfy #9263 by catching decode failures in decodeJwt and returning TokenVerificationError instead of raw SyntaxError.
Out of Scope Changes check ✅ Passed The PR only adds the targeted fix, tests, and changelog entry, with no clear unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

@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

🤖 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/jwt/verifyJwt.ts`:
- Around line 82-96: The JWT parsing flow in verifyJwt must validate that the
decoded header and payload are non-null objects before continuing. After
JSON.parse in the try block, reject null or primitive values using the existing
Invalid JWT form TokenVerificationError path, ensuring malformed tokens such as
null parts cannot reach later destructuring and produce a TypeError.
🪄 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), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: f42cb807-b2d0-4f1c-8dd6-9d4abaa8e863

📥 Commits

Reviewing files that changed from the base of the PR and between 19f7bc6 and 293de4d.

📒 Files selected for processing (3)
  • .changeset/decodejwt-malformed-token.md
  • packages/backend/src/jwt/__tests__/verifyJwt.test.ts
  • packages/backend/src/jwt/verifyJwt.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/cli (auto-detected)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)

Comment on lines +82 to +96
let header, payload, signature;
try {
header = JSON.parse(decoder.decode(base64url.parse(rawHeader, { loose: true })));
payload = JSON.parse(decoder.decode(base64url.parse(rawPayload, { loose: true })));
signature = base64url.parse(rawSignature, { loose: true });
} catch {
return {
errors: [
new TokenVerificationError({
reason: TokenVerificationErrorReason.TokenInvalid,
message: `Invalid JWT form. The header, payload, or signature could not be decoded.`,
}),
],
};
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf '\n== verifyJwt.ts ==\n'
sed -n '1,220p' packages/backend/src/jwt/verifyJwt.ts
printf '\n== search JWT helpers/types ==\n'
rg -n "isJwtObject|type Jwt|interface Jwt|verifyJwt|decodeJwt|TokenVerificationErrorReason" packages/backend/src/jwt packages/backend/src -g '!**/dist/**'

Repository: clerk/javascript

Length of output: 32062


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,220p' packages/backend/src/jwt/verifyJwt.ts
printf '\n---\n'
rg -n "isJwtObject|type Jwt|interface Jwt|verifyJwt|decodeJwt|TokenVerificationErrorReason" packages/backend/src/jwt packages/backend/src -g '!**/dist/**'

Repository: clerk/javascript

Length of output: 32015


🏁 Script executed:

#!/bin/bash
set -euo pipefail
pwd
ls

Repository: clerk/javascript

Length of output: 517


🏁 Script executed:

#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('packages/backend/src/jwt/verifyJwt.ts')
print(p.exists())
print(p)
print(p.read_text()[:4000])
PY

Repository: clerk/javascript

Length of output: 4198


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf '== assertions.ts ==\n'
sed -n '1,260p' packages/backend/src/jwt/assertions.ts
printf '\n== Jwt types from shared package references ==\n'
rg -n "export type Jwt|export interface Jwt|type JwtPayload|interface JwtPayload" packages -g '!**/dist/**'

Repository: clerk/javascript

Length of output: 7586


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf '== assertions.ts ==\n'
sed -n '1,260p' packages/backend/src/jwt/assertions.ts
printf '\n== Jwt types search ==\n'
rg -n "export type Jwt|export interface Jwt|type JwtPayload|interface JwtPayload" packages -g '!**/dist/**'

Repository: clerk/javascript

Length of output: 7562


Reject non-object JWT parts before returning. JSON.parse can return null or a primitive here, and verifyJwt later destructures header/payload inside a catch, so tokens like bnVsbA.bnVsbA. surface a plain TypeError in errors instead of the invalid-form JWT error.

🤖 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/jwt/verifyJwt.ts` around lines 82 - 96, The JWT parsing
flow in verifyJwt must validate that the decoded header and payload are non-null
objects before continuing. After JSON.parse in the try block, reject null or
primitive values using the existing Invalid JWT form TokenVerificationError
path, ensuring malformed tokens such as null parts cannot reach later
destructuring and produce a TypeError.

Sources: Coding guidelines, MCP tools

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.

@clerk/backend: decodeJwt throws a raw SyntaxError for a malformed token

1 participant