Skip to content

add kid to custom JWT header#279

Open
fabiorizzomatos wants to merge 1 commit into
get-convex:mainfrom
fabiorizzomatos:fix/jwt-kid-header
Open

add kid to custom JWT header#279
fabiorizzomatos wants to merge 1 commit into
get-convex:mainfrom
fabiorizzomatos:fix/jwt-kid-header

Conversation

@fabiorizzomatos

@fabiorizzomatos fabiorizzomatos commented Jan 22, 2026

Copy link
Copy Markdown

Context

Convex requires JWTs for customJwt providers to include a kid in the header so it can select the correct key from JWKS.

Problem

@convex-dev/auth generates access tokens without kid. Convex then fails auth with:

"Could not decode token. JWT may be missing a 'kid' (key ID) header."

Fix

Read JWKS from env and include the first key's kid in the JWT protected header.

Changes

  • src/server/implementation/tokens.ts

Summary by CodeRabbit

  • Bug Fixes
    • Improved JWT token generation with optional key ID extraction from JWKS.
    • Enhanced JWT headers to include key ID when available, with graceful fallback if JWKS data is unavailable or invalid.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel

vercel Bot commented Jan 22, 2026

Copy link
Copy Markdown

@fabiorizzomatos is attempting to deploy a commit to the Convex Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jan 22, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The changes add optional JWKS (JSON Web Key Set) support to JWT token generation, extracting a key ID (kid) from JWKS and conditionally including it in the JWT protected header. When JWKS is unavailable or invalid, the system safely falls back to the existing RS256 signing behavior without kid.

Changes

Cohort / File(s) Summary
JWT kid Enhancement
src/server/implementation/tokens.ts
Added optional JWKS handling to extract key ID (kid); updated JWT protected header construction to conditionally include kid when available; maintains existing RS256 signing behavior with safe fallback when kid is unavailable
Dependency Updates
package.json
Manifest updated to support new JWKS functionality (+8/-1 lines)

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A hop through the keys, so secure and bright,
With JWKS whispers, our tokens take flight,
The kid joins the header, when present it's there,
And fallbacks protect us, with utmost care! 🔐✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'add kid to custom JWT header' directly and concisely describes the main change in the pull request, which adds a kid (key ID) to the JWT header.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/server/implementation/tokens.ts`:
- Around line 17-23: The current code silently parses JWKS and picks
jwks.keys[0] into the variable kid, which is unsafe when JWKS is present but
contains multiple keys or is invalid; change tokens.ts to fail fast when JWKS is
set: if requireEnv("JWKS") returns a value, validate that it parses to an object
with a non-empty keys array and deterministically select the signing key by
reading an explicit requireEnv("JWT_KID") (or error if JWT_KID is missing) and
ensure that a matching jwks.keys[].kid exists; if parsing fails or the requested
kid is absent, throw/log an explicit error instead of swallowing it so
misconfiguration is surfaced.

Comment on lines +17 to +23
let kid: string | undefined;
try {
const jwks = JSON.parse(requireEnv("JWKS"));
kid = jwks?.keys?.[0]?.kid;
} catch {
// ignore if JWKS is missing or invalid
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail fast when JWKS is set but doesn’t deterministically map to the signing key.

Selecting jwks.keys[0] is unstable when JWKS has multiple keys (rotation) and may not match the private key used for signing, yielding tokens that consistently fail verification. Also, swallowing invalid JWKS silently hides misconfiguration. Prefer: only ignore when JWKS is absent; if present, validate it and require an explicit selection (e.g., JWT_KID) or error out when ambiguous.

🔧 Proposed fix (deterministic selection + validation)
-  let kid: string | undefined;
-  try {
-    const jwks = JSON.parse(requireEnv("JWKS"));
-    kid = jwks?.keys?.[0]?.kid;
-  } catch {
-    // ignore if JWKS is missing or invalid
-  }
+  let kid: string | undefined;
+  const jwksEnv = process.env.JWKS;
+  if (jwksEnv) {
+    let jwks: any;
+    try {
+      jwks = JSON.parse(jwksEnv);
+    } catch {
+      throw new Error("JWKS env is not valid JSON");
+    }
+    if (!Array.isArray(jwks?.keys) || jwks.keys.length === 0) {
+      throw new Error("JWKS must contain at least one key with a kid");
+    }
+    if (jwks.keys.length > 1 && !process.env.JWT_KID) {
+      throw new Error("JWKS has multiple keys; set JWT_KID to select the signing key");
+    }
+    kid = process.env.JWT_KID ?? jwks.keys[0]?.kid;
+    if (!kid) {
+      throw new Error("Selected JWKS key is missing kid");
+    }
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let kid: string | undefined;
try {
const jwks = JSON.parse(requireEnv("JWKS"));
kid = jwks?.keys?.[0]?.kid;
} catch {
// ignore if JWKS is missing or invalid
}
let kid: string | undefined;
const jwksEnv = process.env.JWKS;
if (jwksEnv) {
let jwks: any;
try {
jwks = JSON.parse(jwksEnv);
} catch {
throw new Error("JWKS env is not valid JSON");
}
if (!Array.isArray(jwks?.keys) || jwks.keys.length === 0) {
throw new Error("JWKS must contain at least one key with a kid");
}
if (jwks.keys.length > 1 && !process.env.JWT_KID) {
throw new Error("JWKS has multiple keys; set JWT_KID to select the signing key");
}
kid = process.env.JWT_KID ?? jwks.keys[0]?.kid;
if (!kid) {
throw new Error("Selected JWKS key is missing kid");
}
}
🤖 Prompt for AI Agents
In `@src/server/implementation/tokens.ts` around lines 17 - 23, The current code
silently parses JWKS and picks jwks.keys[0] into the variable kid, which is
unsafe when JWKS is present but contains multiple keys or is invalid; change
tokens.ts to fail fast when JWKS is set: if requireEnv("JWKS") returns a value,
validate that it parses to an object with a non-empty keys array and
deterministically select the signing key by reading an explicit
requireEnv("JWT_KID") (or error if JWT_KID is missing) and ensure that a
matching jwks.keys[].kid exists; if parsing fails or the requested kid is
absent, throw/log an explicit error instead of swallowing it so misconfiguration
is surfaced.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant