') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); fix(backend): Require configured JWT header type by jescalan · Pull Request #8471 · clerk/javascript · GitHub
Skip to content

fix(backend): Require configured JWT header type - #8471

Merged
wobsoriano merged 4 commits into
mainfrom
codex/fix-verify-token-header-type
May 13, 2026
Merged

fix(backend): Require configured JWT header type#8471
wobsoriano merged 4 commits into
mainfrom
codex/fix-verify-token-header-type

Conversation

@jescalan

Copy link
Copy Markdown
Contributor

Summary

This changes verifyJwt / verifyToken header type validation so a missing JWT typ header is only accepted when callers have not configured headerType.

When callers explicitly pass headerType, the token must now include a typ header that matches one of the configured values. For example, headerType: 'at+jwt' now rejects a token whose JOSE header omits typ.

Root Cause

assertHeaderType returned early whenever typ was undefined, before it checked the configured allowlist. That meant an explicit headerType option could be silently skipped for typ-less tokens.

The default verifier behavior is preserved for compatibility: if headerType is omitted, a missing typ still passes.

Tests

  • Added direct assertion coverage for missing typ with and without configured allowed types.
  • Added verifier coverage for a token without typ when headerType: 'at+jwt' is configured.

Validation

  • NODE_OPTIONS=--no-experimental-webstorage pnpm --filter @clerk/backend build
  • pnpm --filter @clerk/backend build:runtime
  • NODE_OPTIONS=--no-experimental-webstorage pnpm exec vitest run src/jwt/__tests__/assertions.test.ts src/jwt/__tests__/verifyJwt.test.ts --environment node --typecheck.enabled=false
  • pnpm --filter @clerk/backend format:check
  • git diff --check

@vercel

vercelBot commented May 4, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentMay 13, 2026 6:51pm

Request Review

@changeset-bot

changeset-botBot commented May 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 85bfdad

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

This PR includes changesets to release 10 packages
NameType
@clerk/backendPatch
@clerk/astroPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch

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

@jescalanjescalan changed the title [codex] Require configured JWT header typeRequire configured JWT header typeMay 4, 2026
@jescalan
jescalan marked this pull request as ready for review May 4, 2026 19:16
@jescalanjescalan changed the title Require configured JWT header typefix(backend): Require configured JWT header typeMay 4, 2026
@coderabbitai

coderabbitaiBot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro

Run ID: 8ada79a8-6e8b-4ebd-b719-995d71c2c377

📥 Commits

Reviewing files that changed from the base of the PR and between ddc3c8d and ab16250.

📒 Files selected for processing (1)
  • .changeset/tidy-chicken-bathe.md
✅ Files skipped from review due to trivial changes (1)
  • .changeset/tidy-chicken-bathe.md

📝 Walkthrough

Walkthrough

assertHeaderType's signature was changed to make allowedTypes optional and its early-return now occurs only when both typ and allowedTypes are undefined. When allowedTypes is provided, the function computes expectedTypes as allowedTypes ?? 'JWT', normalizes to an array, and validates typ against it, throwing TokenVerificationError for missing or unexpected typ. Tests were updated: assertions.test now checks both the no-throw case when allowedTypes is not configured and the throw case when it is; verifyJwt.test adds a case asserting verification fails with configured headerType. A changeset documents the behavior change.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately summarizes the main change: requiring a configured JWT header type when validating tokens.
Description check✅ PassedThe description is well-related to the changeset, clearly explaining the root cause, the change behavior, tests added, and validation steps performed.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@pkg-pr-new

pkg-pr-newBot commented May 6, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8471

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 85bfdad

@wobsoriano
wobsoriano merged commit 2377305 into mainMay 13, 2026
43 checks passed
@wobsoriano
wobsoriano deleted the codex/fix-verify-token-header-type branch May 13, 2026 19:06
Sign up for freeto 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.

3 participants

@jescalan@dominic-clerk@wobsoriano