-
Notifications
You must be signed in to change notification settings - Fork 39
New topic - Decode JWTs #579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds 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
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
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests
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. Comment |
There was a problem hiding this 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-29Please 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 applicableI 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 explicitJWTDecoded
import and tighten the decoded payload exampleImport the exported
JWTDecoded
type and pass a concrete payload shape tojwtDecoder
:-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
📒 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 ofsdk
orsdks
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";
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
New topic about JWT decoding, referencing the Kinde utils we have to do this.
Needs review by @DanielRivers at minimum.
Summary by CodeRabbit