Skip to content

Conversation

clairekinde11
Copy link
Collaborator

@clairekinde11 clairekinde11 commented Sep 29, 2025

New topic about JWT decoding, referencing the Kinde utils we have to do this.
Needs review by @DanielRivers at minimum.

Summary by CodeRabbit

  • Documentation
    • Added a new guide on decoding and decrypting JWTs using Kinde’s JWT libraries.
    • Includes installation steps and examples for JWT Decoder and Validator, with type-safe usage.
    • Covers manual decoding approaches, validation checklist, and security considerations.
    • Provides common use cases (user info, feature flags, permissions) with example snippets.
    • Details error handling patterns and best practices for sequencing validation and logging.
    • Available for JavaScript and TypeScript audiences.

Copy link
Contributor

coderabbitai bot commented Sep 29, 2025

Walkthrough

Adds a new documentation page at src/content/docs/build/tokens/decode-jwts.mdx explaining JWT structure and how to decode/validate JWTs using Kinde JWT libraries, plus manual decoding approaches, examples, security considerations, validation checklist, use cases, error handling, and best practices.

Changes

Cohort / File(s) Summary
Docs — JWT decoding/validation guide
src/content/docs/build/tokens/decode-jwts.mdx
New MDX page with front matter and sections covering JWT structure, Kinde JWT Decoder/Validator installation and usage, manual decoding, validation checklist, examples (JS/TS), error handling, and best practices.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Dev App
  participant JWT Decoder as Kinde JWT Decoder
  participant JWT Validator as Kinde JWT Validator
  participant Resource as Protected API

  rect rgb(245,250,255)
  note over Dev App: Input JWT from auth flow
  Dev App->>JWT Decoder: decode(token)
  JWT Decoder-->>Dev App: header, payload, signature (no trust)
  end

  alt Validate signature and claims
    Dev App->>JWT Validator: validateToken(token, options)
    JWT Validator-->>Dev App: { valid, claims | error }
    opt On success
      Dev App->>Resource: attach token / use claims
      Resource-->>Dev App: response
    end
    opt On failure
      Dev App-->>Dev App: handle error (log, deny)
    end
  else Decode only (display non-sensitive info)
    Dev App-->>Dev App: use decoded payload cautiously
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

I nibbled claims from tokens bright,
Unwrapped their headers by moonlight.
With keys in paw, I checked the sign,
Then hopped through scopes in tidy line.
Decode, validate—no bugs in sight—
Carrots secured, my payload’s right. 🥕✨

Pre-merge checks and finishing touches

✅ 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 “New topic – Decode JWTs” succinctly captures the primary change by indicating the addition of a new documentation topic focused on decoding JWTs. It avoids extraneous detail while clearly conveying the main update, making it understandable in a quick glance. Although the prefix “New topic” is slightly generic, it does not obscure the key subject of the changeset. Overall, the title aligns with the main objective of adding a decode-JWTs guide.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch Feat/DecodeJWTs

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (8)
src/content/docs/build/tokens/decode-jwts.mdx (8)

30-30: Update the “updated” date to this PR’s timeline.

Current: 2024-01-15. Suggest 2025-09-29 for accuracy.

-updated: 2024-01-15
+updated: 2025-09-29

Please confirm the doc site’s date policy (authored vs updated).


114-125: Show explicit expected algorithms and domain/issuer guidance.

Add recommended options and a note to pin expected algs (e.g., RS256) and issuer.

-const validationResult = await validateToken({
-  token: "eyJhbGc...",
-  domain: "https://your-subdomain.kinde.com"
-});
+const validationResult = await validateToken({
+  token: "eyJhbGc...",
+  domain: "https://your-subdomain.kinde.com",
+  // Optional but recommended hardening:
+  expectedAlgs: ["RS256"],
+  expectedIssuer: "https://your-subdomain.kinde.com",
+});

I can add a short “why pin algorithms” callout if helpful.


166-191: Provide a complete TS implementation rather than a placeholder.

Avoid “Implementation same as above”; include the base64url helper and typed return.

-interface DecodedJWT {
+interface DecodedJWT {
   header: JWTHeader;
   payload: JWTPayload;
   signature: string;
 }
 
-function decodeJWT(token: string): DecodedJWT {
-  // Implementation same as above
-}
+const b64urlToString = (b64url: string): string => {
+  const b64 = b64url.replace(/-/g, '+').replace(/_/g, '/');
+  const padded = b64 + '==='.slice((b64.length + 3) % 4);
+  // Browser vs Node
+  return typeof atob === 'function'
+    ? decodeURIComponent(escape(atob(padded)))
+    : Buffer.from(padded, 'base64').toString('utf8');
+};
+
+export function decodeJWT(token: string): DecodedJWT {
+  const parts = token.split('.');
+  if (parts.length !== 3) throw new Error('Invalid JWT format');
+  const header = JSON.parse(b64urlToString(parts[0])) as JWTHeader;
+  const payload = JSON.parse(b64urlToString(parts[1])) as JWTPayload;
+  return { header, payload, signature: parts[2] };
+}

197-201: Tighten the security bullets with explicit “don’t trust until validated.”

Clarify sequencing to prevent misinterpretation.

-**Decoding vs. Validation**: Decoding a JWT only extracts the payload - it doesn't verify the token's authenticity or integrity.
-**Always Validate**: After decoding, always validate the token using proper cryptographic verification.
+**Decoding vs. Validation**: Decoding only parses claims; it does not prove authenticity/integrity.
+**Always Validate**: Perform cryptographic validation (signature + claims) and only then trust any decoded data.

204-212: Expand the validation checklist (alg, kid/JWKS, nbf/leeway, issuer, audience).

Add commonly missed checks and adjust terminology.

-When validating JWTs, ensure you:
+When validating JWTs, ensure you:
 - Verify the token signature using the public key
 - Check the `iss` (issuer) claim matches your Kinde domain
 - Validate the `aud` (audience) claim
 - Verify the `exp` (expiration) claim
 - Check the `iat` (issued at) claim is reasonable
 - Validate any custom claims specific to your application
+- Enforce expected `alg` (e.g., RS256) and reject `none` or unexpected algorithms
+- Use `kid` to select the correct key from JWKS and cache JWKS with rotation in mind
+- Check `nbf` (not before) and allow small clock skew (e.g., ±60s leeway)
+- For OIDC ID tokens, also verify `nonce` when applicable

I can add a short JWKS caching callout if you want.


245-260: Harden feature‑flag access with nullish chaining and typing.

Avoid runtime errors when claims are absent.

-function checkFeatureFlag(token, flagName) {
+function checkFeatureFlag(token, flagName) {
   try {
     const decoded = jwtDecoder(token);
-    const featureFlags = decoded.payload.feature_flags;
-    
-    if (featureFlags && featureFlags[flagName]) {
-      return featureFlags[flagName].v; // 'v' is the value
-    }
-    
-    return false;
+    const v = decoded?.payload?.feature_flags?.[flagName]?.v;
+    return v ?? false;
   } catch (error) {
     console.error('Failed to check feature flag:', error);
     return false;
   }
 }

306-308: Rephrase “Validate Before Decoding.”

You must parse to validate; intent is “don’t trust decoded contents until validated.”

-**Validate Before Decoding**: Always validate the token's signature and claims before trusting the decoded payload.
+**Validate before trusting**: Parse, validate signature and claims, then trust the decoded payload.

68-75: Add explicit JWTDecoded import and tighten the decoded payload example

Import the exported JWTDecoded type and pass a concrete payload shape to jwtDecoder:

-import { jwtDecoder } from "@kinde/jwt-decoder";
+import { jwtDecoder, type JWTDecoded } from "@kinde/jwt-decoder";

Define a custom payload type and apply it:

-const decodedToken = jwtDecoder("eyJhbGc...");
+type MyClaims = JWTDecoded["payload"] & {
+  custom_claim?: string;
+  feature_flags?: Record<string, { v: unknown }>;
+};
+
+const decodedToken = jwtDecoder<MyClaims>("eyJhbGc...");

Apply the same pattern in the example at lines 79–90.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d88d65d and 94210b3.

📒 Files selected for processing (1)
  • src/content/docs/build/tokens/decode-jwts.mdx (1 hunks)
🔇 Additional comments (2)
src/content/docs/build/tokens/decode-jwts.mdx (2)

5-11: Inspect front-matter in decode-jwts.mdx
Please confirm presence and naming of sdk or sdks key in the front-matter once we have the file’s first lines.


112-112: Remove unused type import
jwtValidationResponse isn’t referenced in this file; drop it to avoid confusion.

-import { validateToken, type jwtValidationResponse } from "@kinde/jwt-validator";
+import { validateToken } from "@kinde/jwt-validator";

Comment on lines +3 to +4
title: Decrypting JSON Web Tokens
description: Learn how to decrypt and decode JSON Web Tokens (JWTs) using Kinde's JWT libraries, including validation methods, security considerations, and practical implementation examples.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use “Decoding and validating” (not “Decrypting”) and align terms across page.

“Decrypting” implies JWE. Most Kinde tokens are signed JWS. To avoid security confusion, rename the title/sections/keywords and wording that suggest decryption, and keep a brief note about JWE as an edge case.

Apply this diff to front matter and section headings/phrasing shown in this file:

-title: Decrypting JSON Web Tokens
-description: Learn how to decrypt and decode JSON Web Tokens (JWTs) using Kinde's JWT libraries, including validation methods, security considerations, and practical implementation examples.
+title: Decoding and validating JSON Web Tokens
+description: Learn how to decode and validate JSON Web Tokens (JWTs) using Kinde’s libraries, with security considerations and practical examples.

 topics:
   - tokens
   - security
   - jwt
-  - decryption
+  - validation

 keywords:
-  - JWT decryption
+  - JWT decoding
   - token decoding
   - JWT validation
   - token security
   - JWT libraries
   - token parsing
-ai_summary: Comprehensive guide to decrypting and decoding JSON Web Tokens using Kinde's JWT libraries, including validation methods, security considerations, and practical implementation examples.
+ai_summary: Guide to decoding and validating JSON Web Tokens using Kinde’s libraries, with security considerations and practical examples.

And in body:

-Before decrypting a JWT, it's important to understand its structure.
+Before decoding a JWT, it's important to understand its structure.

-## Methods for Decrypting JWTs
+## Methods for decoding and validating JWTs

-When decrypting JWTs, ensure you:
+When validating JWTs, ensure you:

Optional JWE note near the intro:

+Note: If you actually receive encrypted JWTs (JWE), you must decrypt them before decoding/validation. Kinde issues signed JWTs (JWS) by default.

Also applies to: 12-16, 24-29, 46-46, 40-40, 204-204, 33-33

🤖 Prompt for AI Agents
In src/content/docs/build/tokens/decode-jwts.mdx around lines 3-4 (and also
ensure changes at 12-16, 24-29, 33-33, 40-40, 46-46, 204-204), replace the
front-matter title and any section headings or copy that use “Decrypting” with
“Decoding and validating” and update related keywords/description to reflect
decoding/validation of JWS (signed JWTs) rather than decryption; revise body
text to avoid implying JWE unless explicitly noting it, and add a brief
clarifying note near the introduction that JWE (encrypted JWTs) is an edge case
and requires different handling with a pointer to a dedicated page; ensure all
occurrences of “decrypt/decrypting/decryption” are changed to
“decode/decoding/decoding and validating” or equivalent context-appropriate
phrasing and update examples and headings accordingly.

Comment on lines +134 to +155
function decodeJWT(token) {
try {
// Split the token into its three parts
const parts = token.split('.');

if (parts.length !== 3) {
throw new Error('Invalid JWT format');
}

// Decode header and payload (base64url)
const header = JSON.parse(atob(parts[0].replace(/-/g, '+').replace(/_/g, '/')));
const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));

return {
header,
payload,
signature: parts[2]
};
} catch (error) {
throw new Error('Failed to decode JWT: ' + error.message);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Base64url decoding needs padding and Node support; current snippet breaks outside browsers and with unicode.

atob isn’t available in Node and base64url often needs '=' padding. Provide a cross‑runtime helper.

-function decodeJWT(token) {
+function b64urlToString(b64url) {
+  const b64 = b64url.replace(/-/g, '+').replace(/_/g, '/');
+  const padded = b64 + '==='.slice((b64.length + 3) % 4);
+  if (typeof atob === 'function') {
+    // Browser
+    return decodeURIComponent(escape(atob(padded)));
+  }
+  // Node
+  return Buffer.from(padded, 'base64').toString('utf8');
+}
+
+function decodeJWT(token) {
   try {
     // Split the token into its three parts
     const parts = token.split('.');
     
     if (parts.length !== 3) {
       throw new Error('Invalid JWT format');
     }
     
     // Decode header and payload (base64url)
-    const header = JSON.parse(atob(parts[0].replace(/-/g, '+').replace(/_/g, '/')));
-    const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
+    const header = JSON.parse(b64urlToString(parts[0]));
+    const payload = JSON.parse(b64urlToString(parts[1]));
     
     return {
       header,
       payload,
       signature: parts[2]
     };
   } catch (error) {
     throw new Error('Failed to decode JWT: ' + error.message);
   }
 }
📝 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
function decodeJWT(token) {
try {
// Split the token into its three parts
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid JWT format');
}
// Decode header and payload (base64url)
const header = JSON.parse(atob(parts[0].replace(/-/g, '+').replace(/_/g, '/')));
const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
return {
header,
payload,
signature: parts[2]
};
} catch (error) {
throw new Error('Failed to decode JWT: ' + error.message);
}
}
function b64urlToString(b64url) {
const b64 = b64url.replace(/-/g, '+').replace(/_/g, '/');
const padded = b64 + '==='.slice((b64.length + 3) % 4);
if (typeof atob === 'function') {
// Browser
return decodeURIComponent(escape(atob(padded)));
}
// Node
return Buffer.from(padded, 'base64').toString('utf8');
}
function decodeJWT(token) {
try {
// Split the token into its three parts
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid JWT format');
}
// Decode header and payload (base64url)
const header = JSON.parse(b64urlToString(parts[0]));
const payload = JSON.parse(b64urlToString(parts[1]));
return {
header,
payload,
signature: parts[2]
};
} catch (error) {
throw new Error('Failed to decode JWT: ' + error.message);
}
}
🤖 Prompt for AI Agents
In src/content/docs/build/tokens/decode-jwts.mdx around lines 134 to 155, the
snippet uses atob and raw base64url decoding which fails in Node and for UTF-8
content; replace the inline atob usage with a small cross-runtime base64url
decode helper that (1) converts base64url chars to base64, (2) adds required '='
padding, (3) decodes using Buffer.from(..., 'base64') when
Buffer/globalThis.Buffer exists or falls back to globalThis.atob in browsers,
and (4) handles UTF-8 by percent-decoding the binary string to a Unicode string
before JSON.parse; then call this helper for both header and payload and surface
the original error message on failure.

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