jsonwebtoken
>=9.0.0postconditions15functions3last verified2026-06-23coverage score100%Postconditions: what we check
- verify · verify-token-expirederrorWhentoken's exp claim is before current timeThrows
TokenExpiredError: jwt expiredRequired handlingCaller MUST wrap jwt.verify() in try-catch block or use callback error-first pattern. TokenExpiredError indicates legitimate expiration - handle gracefully with 401 response and prompt user to refresh token or re-authenticate.costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[1] - verify · verify-token-not-activeerrorWhentoken's nbf claim is after current timeThrows
NotBeforeError: jwt not activeRequired handlingCaller MUST handle NotBeforeError. Token is valid but not yet active. Either reject with 401 or retry after specified date.costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[1] - verify · verify-invalid-signatureerrorWhentoken signature does not match expected valueThrows
JsonWebTokenError: invalid signatureRequired handlingCaller MUST handle JsonWebTokenError for invalid signatures. This indicates tampering or wrong secret/key. CRITICAL security event - log and reject with 403. Never expose error details to client.costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[1] - verify · verify-malformed-tokenerrorWhentoken structure is invalid (not 3 parts, invalid base64, etc.)Throws
JsonWebTokenError: jwt malformedRequired handlingCaller MUST handle JsonWebTokenError for malformed tokens. Invalid structure indicates corrupted token or attack. Reject with 400 or 403.costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[1] - verify · verify-invalid-algorithmerrorWhentoken algorithm not in options.algorithms whitelistThrows
JsonWebTokenError: invalid algorithmRequired handlingCaller MUST handle algorithm mismatch errors. This prevents CVE-2015-9235 algorithm confusion attack. Always specify algorithms option in verify().costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[2] - verify · verify-audience-mismatcherrorWhentoken aud claim does not match options.audienceThrows
JsonWebTokenError: jwt audience invalid. expected: [expected]Required handlingCaller MUST handle audience validation errors when using options.audience. Audience mismatch indicates token intended for different service.costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[3] - verify · verify-issuer-mismatcherrorWhentoken iss claim does not match options.issuerThrows
JsonWebTokenError: jwt issuer invalid. expected: [expected]Required handlingCaller MUST handle issuer validation errors when using options.issuer. Issuer mismatch indicates token from untrusted source.costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[3] - verify · verify-missing-secreterrorWhensecretOrPublicKey parameter is undefined or emptyThrows
JsonWebTokenError: secret or public key must be providedRequired handlingCaller MUST handle missing secret errors. This typically indicates configuration error (missing environment variable). Fatal error - should fail fast on application startup, not at runtime.costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[4] - sign · sign-invalid-payloadwarningWhenpayload is not a plain object, string, or bufferThrows
Error: Expected 'payload' to be a plain object, Buffer, or stringRequired handlingCaller SHOULD validate payload type before calling jwt.sign() or wrap in try-catch. Common when payload is null, undefined, or Promise object (forgot await on database query).costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[5] - sign · sign-missing-secretwarningWhensecretOrPrivateKey is undefined, null, or emptyThrows
Error: secretOrPrivateKey must have a valueRequired handlingCaller SHOULD ensure secret exists before calling jwt.sign(). Missing secret indicates configuration error. Should fail fast on startup, not at runtime during login.costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[5] - sign · sign-invalid-optionswarningWhenoptions.algorithm is invalid or unsupportedThrows
Error: 'algorithm' must be a valid string enum valueRequired handlingCaller SHOULD validate algorithm option. Common mistake: typo in algorithm name (e.g., 'HS-256' instead of 'HS256').costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[5] - sign · sign-invalid-expiresinwarningWhenoptions.expiresIn is invalid formatThrows
Error: invalid expiresIn optionRequired handlingCaller SHOULD validate expiresIn format. Accepts seconds (number) or time span string ('1h', '2d', '30s'). Invalid format throws.costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[6] - sign · sign-claim-conflictwarningWhenoptions.expiresIn provided but payload already has exp propertyThrows
Error: Bad 'options.expiresIn' option the payload already has an 'exp' propertyRequired handlingCaller SHOULD NOT set exp in both payload and options. Choose one method: either payload.exp or options.expiresIn, not both.costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisibleSources[6] - decode · decode-used-for-authenticationerrorWhenjwt.decode() return value is used to make authentication or authorization decisions (e.g., checking role claims, user ID, or admin status) without subsequently calling jwt.verify() with the same token.ReturnsJwtPayload | null | stringRequired handlingMUST use jwt.verify() for all authentication and authorization decisions. jwt.decode() is only safe for: (1) inspecting the header to select a verification key from a JWKS, (2) extracting non-security metadata after verification has already succeeded, (3) debugging and logging. Never use decode() result to make access control decisions.costcriticalin prodimmediate exceptionusers seesecurity breachvisibilitysilent
- decode · decode-null-return-not-checkedwarningWhenjwt.decode() return value is used without checking for null, on input that may be untrusted, undefined, or malformed.Returnsnull | JwtPayload | stringRequired handlingAlways check the return value before accessing properties: const payload = jwt.decode(token); if (!payload) { return res.status(400).json({ error: 'Invalid token' }); }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[7]
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
- [8]owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing10 Testing JSON Web Tokens
- [9]invicti.com/blog/web-security/json-web-token-jwt-attacks-vulnerabilitiesJson Web Token Jwt Attacks Vulnerabilities
- [1]github.com/auth0/node-jsonwebtokenauth0/node-jsonwebtoken
- [3]github.com/auth0/node-jsonwebtokenauth0/node-jsonwebtoken
- [4]github.com/auth0/node-jsonwebtokenauth0/node-jsonwebtoken
- [5]github.com/auth0/node-jsonwebtokenauth0/node-jsonwebtoken
- [6]github.com/auth0/node-jsonwebtokenauth0/node-jsonwebtoken
- [7]github.com/auth0/node-jsonwebtoken/blobauth0/node-jsonwebtoken · README.md
- [2]nvd.nist.gov/vuln/detail/CVE-2015-9235CVE 2015 9235
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
Sources: jsonwebtoken
Official Documentation
- npm: https://www.npmjs.com/package/jsonwebtoken
- GitHub: https://github.com/auth0/node-jsonwebtoken
- README: https://github.com/auth0/node-jsonwebtoken/blob/master/README.md
- Auth0 JWT Docs: https://auth0.com/docs/secure/tokens/json-web-tokens
Error Types and Handling
TokenExpiredError
Thrown when token's exp claim is before current time.
Properties:
name: 'TokenExpiredError'message: 'jwt expired'expiredAt: Date- timestamp when token expired
Source: https://github.com/auth0/node-jsonwebtoken#errors--codes
JsonWebTokenError
General error for invalid tokens, signatures, algorithms, claims, etc.
Common Messages:
'jwt malformed'- invalid token structure'invalid signature'- signature verification failed'invalid algorithm'- algorithm not in whitelist'jwt audience invalid. expected: [expected]'- audience mismatch'jwt issuer invalid. expected: [expected]'- issuer mismatch
Source: https://github.com/auth0/node-jsonwebtoken#errors--codes
NotBeforeError
Thrown when current time is before token's nbf claim.
Properties:
name: 'NotBeforeError'message: 'jwt not active'date: Date- when token becomes valid
Source: https://github.com/auth0/node-jsonwebtoken#errors--codes
Security Vulnerabilities
CVE-2015-9235: Algorithm Confusion Attack
CVSS: 7.5 HIGH Affected: jsonwebtoken < 4.2.2 Fixed: v4.2.2 (2015)
Description: Attacker can bypass signature verification by changing algorithm from asymmetric (RS256) to symmetric (HS256) and using public key as HMAC secret.
Attack Vector:
- Server uses RS256 with RSA keypair
- Attacker obtains public key (usually public info)
- Attacker creates token with
alg: HS256in header - Attacker signs with HMAC-SHA256 using public key as secret
- Server verifies with public key as HMAC secret (instead of RSA verification)
- Signature validates! Authentication bypassed.
Mitigation:
Always specify algorithms option in jwt.verify():
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
Sources:
- https://nvd.nist.gov/vuln/detail/CVE-2015-9235
- https://github.com/advisories/GHSA-c7hr-j4mj-j2w6
- https://security.snyk.io/vuln/npm:jsonwebtoken:20150331
- https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
CVE-2022-23529: JWT Secret Poisoning
CVSS: 7.6 HIGH Affected: jsonwebtoken <= 8.5.1 Fixed: v9.0.0 (December 2022)
Description:
Malicious actor can inject arbitrary objects into server's JavaScript runtime through insecure deserialization in jwt.verify(), potentially leading to Remote Code Execution (RCE).
Requirements for Exploitation:
- Attacker can modify key retrieval parameter
- Application passes user-controlled input to jwt.verify()
- Attacker crafts malicious object that triggers code execution
Mitigation:
- Upgrade to jsonwebtoken >= 9.0.0
- Never use user input as secret
- Load keys from trusted sources only
- Validate all inputs before passing to JWT functions
Sources:
- https://unit42.paloaltonetworks.com/jsonwebtoken-vulnerability-cve-2022-23529/
- https://github.com/advisories/GHSA-8cf7-32gw-wr33
- https://anchore.com/blog/finding-and-fixing-the-jsonwebtoken-vulnerabilities/
CVE-2022-23540: Invalid Token Parsing
CVSS: 5.9 MEDIUM Affected: jsonwebtoken <= 8.5.1 Fixed: v9.0.0 (December 2022)
Description:
In certain edge cases, jwt.verify() can fail to properly validate malformed tokens, potentially allowing invalid tokens to be accepted.
Mitigation: Upgrade to jsonwebtoken >= 9.0.0
Common Mistakes and Antipatterns
1. Using jwt.decode() for Authentication
CRITICAL SECURITY BUG
jwt.decode() does NOT verify signatures - it only decodes the token.
Vulnerable Code:
const decoded = jwt.decode(userToken);
if (decoded.isAdmin) {
grantAdminAccess(); // ATTACKER CAN FORGE TOKENS\!
}
Correct Code:
try {
const decoded = jwt.verify(userToken, secret, { algorithms: ['HS256'] });
if (decoded.isAdmin) {
grantAdminAccess(); // Signature verified
}
} catch (error) {
// Invalid token
}
Sources:
- https://github.com/nextauthjs/next-auth/issues/748
- https://www.invicti.com/blog/web-security/json-web-token-jwt-attacks-vulnerabilities/
2. Missing Error Handling on verify()
Common Pattern:
// BUG: No try-catch - crashes on invalid/expired token
const decoded = jwt.verify(token, secret);
console.log(decoded.userId);
Correct Pattern:
try {
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
console.log(decoded.userId);
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
// Handle expiration
} else if (error instanceof jwt.JsonWebTokenError) {
// Handle invalid token
}
}
3. Not Checking Callback Error Parameter
Vulnerable Code:
jwt.verify(token, secret, (err, decoded) => {
console.log(decoded.userId); // BUG: decoded undefined if err exists\!
});
Correct Code:
jwt.verify(token, secret, (err, decoded) => {
if (err) {
console.error('Verification failed:', err.message);
return;
}
console.log(decoded.userId);
});
4. Missing algorithms Option
Vulnerable (CVE-2015-9235):
jwt.verify(token, publicKey); // No algorithm whitelist\!
Secure:
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
5. Exposing Error Details to Clients
Bad Practice:
catch (error) {
res.status(401).json({ error: error.message });
// Exposes: "invalid signature", "jwt malformed", etc.
}
Best Practice:
catch (error) {
console.error('JWT error:', error.message); // Log internally
res.status(401).json({ error: 'Unauthorized' }); // Generic message
}
Best Practices
1. Always Wrap verify() in Try-Catch
try {
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'],
audience: 'myapp',
issuer: 'auth-service'
});
return decoded;
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
// Prompt user to refresh token
} else if (error instanceof jwt.JsonWebTokenError) {
// Invalid token
}
throw error;
}
2. Always Specify algorithms Option
// HS256 for symmetric (shared secret)
jwt.verify(token, secret, { algorithms: ['HS256'] });
// RS256 for asymmetric (public/private key)
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
3. Always Set Token Expiration
const token = jwt.sign(
{ userId: 123 },
secret,
{
expiresIn: '15m', // Access token
algorithm: 'HS256'
}
);
Recommended Expiration:
- Access tokens: 15 minutes to 1 hour
- Refresh tokens: 7 to 30 days
4. Use Strong Secrets
For HMAC algorithms (HS256, HS384, HS512):
// ❌ WEAK
const secret = 'password123';
// ✅ STRONG
const secret = crypto.randomBytes(64).toString('hex');
Minimum Secret Strength:
- HS256: 256+ bits (32+ bytes)
- HS384: 384+ bits (48+ bytes)
- HS512: 512+ bits (64+ bytes)
5. Validate Claims
jwt.verify(token, secret, {
algorithms: ['HS256'],
audience: 'myapp', // Validate aud claim
issuer: 'auth-service', // Validate iss claim
maxAge: '2h' // Additional age limit
});
6. Handle Specific Error Types
catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return { valid: false, reason: 'expired', expiredAt: error.expiredAt };
}
if (error instanceof jwt.NotBeforeError) {
return { valid: false, reason: 'not-active', date: error.date };
}
if (error instanceof jwt.JsonWebTokenError) {
return { valid: false, reason: 'invalid' };
}
throw error; // Unexpected error
}
7. NEVER Use decode() for Authentication
// ✅ ONLY use decode() for debugging
const decoded = jwt.decode(token, { complete: true });
console.log('Token header:', decoded?.header);
console.log('Token payload:', decoded?.payload);
// Do NOT make security decisions based on this\!
// ✅ ALWAYS use verify() for authentication
try {
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
// Now safe to make security decisions
} catch (error) {
// Invalid token
}
Supported Algorithms
HMAC (Symmetric - Shared Secret)
HS256- HMAC using SHA-256 (most common for symmetric)HS384- HMAC using SHA-384HS512- HMAC using SHA-512
RSA (Asymmetric - Public/Private Key)
RS256- RSASSA-PKCS1-v1_5 using SHA-256RS384- RSASSA-PKCS1-v1_5 using SHA-384RS512- RSASSA-PKCS1-v1_5 using SHA-512
ECDSA (Asymmetric - Elliptic Curve)
ES256- ECDSA using P-256 and SHA-256ES384- ECDSA using P-384 and SHA-384ES512- ECDSA using P-521 and SHA-512
PSS (Asymmetric - Probabilistic Signature)
PS256- RSASSA-PSS using SHA-256PS384- RSASSA-PSS using SHA-384PS512- RSASSA-PSS using SHA-512
None (DANGEROUS - No Signature)
none- No signature verification
WARNING: Never allow none algorithm in production! Always whitelist specific algorithms.
Minimum Safe Version
Recommended: >=9.0.0
Rationale:
- CVE-2015-9235 fixed in 4.2.2
- CVE-2022-23529 fixed in 9.0.0
- CVE-2022-23540 fixed in 9.0.0
- Modern security improvements in 9.x
- Active maintenance
Latest Version (2026-02-27): v9.0.2
Contract Justification
This contract requires error handling because:
-
jwt.verify() is a critical security boundary
- Throws TokenExpiredError, NotBeforeError, JsonWebTokenError
- Missing error handling = authentication bypass or crash
- Security decisions depend on proper error handling
-
jwt.sign() can throw on invalid inputs
- Invalid payload, secret, or options cause errors
- Missing error handling = crash during login/token generation
-
Common mistakes are security-critical
- Using decode() instead of verify() = complete auth bypass
- Missing algorithms option = vulnerable to CVE-2015-9235
- Not checking callback errors = undefined behavior
-
Error types indicate different security states
- TokenExpiredError: Legitimate expiration (refresh needed)
- JsonWebTokenError: Invalid/tampered token (reject access)
- NotBeforeError: Token not yet valid (retry later)
The library is designed to fail fast on security violations, making proper error handling essential for both security and reliability.