This error occurs when the jsonwebtoken library fails to parse or decode a JWT token during verification. The token structure is malformed, corrupted, or contains invalid encoding that prevents successful parsing.
The "JsonWebTokenError: invalid token" error is thrown by the Node.js jsonwebtoken library when it cannot successfully parse or decode a JWT (JSON Web Token) during the verification process. This is a structural parsing failure, meaning the library encountered a token that doesn't meet the basic format requirements of a valid JWT. A properly formatted JWT must consist of exactly three Base64URL-encoded components separated by dots: header.payload.signature. When jwt.verify() receives a token that doesn't match this structure or contains invalid encoding, it throws this error to indicate the token cannot be decoded. This error is distinct from "invalid signature" (cryptographic verification failure) and "TokenExpiredError" (expired but valid token). The "invalid token" error specifically means the token's structure or encoding is fundamentally broken, preventing even basic parsing before any signature verification or expiration checks can occur.
Before calling jwt.verify(), check that the token is a valid string:
const jwt = require('jsonwebtoken');
function verifyToken(token) {
// Check for null, undefined, or empty string
if (!token || typeof token !== 'string') {
throw new Error('Token must be a non-empty string');
}
console.log('Token value:', token);
console.log('Token length:', token.length);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
return decoded;
} catch (error) {
console.error('JWT verification failed:', error.message);
throw error;
}
}This validates the token type and logs its value for debugging.
Validate that the token has exactly three dot-separated components:
function validateTokenStructure(token) {
const parts = token.split('.');
if (parts.length !== 3) {
console.error(`Invalid JWT structure: expected 3 parts, got ${parts.length}`);
console.error('Token parts:', parts);
throw new Error('JWT must have exactly three dot-separated parts (header.payload.signature)');
}
// Check each part is non-empty
if (parts.some(part => !part || part.length === 0)) {
throw new Error('JWT parts cannot be empty');
}
console.log('Token structure valid: header, payload, signature present');
return true;
}
// Use before verification
const token = req.headers.authorization?.replace('Bearer ', '');
validateTokenStructure(token);
const decoded = jwt.verify(token, process.env.JWT_SECRET);This ensures the basic JWT format is correct.
Remove "Bearer " or "JWT " prefixes from the Authorization header:
function extractToken(authHeader) {
if (!authHeader) {
throw new Error('Authorization header missing');
}
// Handle different authentication schemes
let token = authHeader;
if (authHeader.startsWith('Bearer ')) {
token = authHeader.substring(7); // Remove 'Bearer '
} else if (authHeader.startsWith('JWT ')) {
token = authHeader.substring(4); // Remove 'JWT '
}
// Trim whitespace
token = token.trim();
console.log('Extracted token:', token.substring(0, 20) + '...');
return token;
}
// Express middleware example
app.use((req, res, next) => {
try {
const authHeader = req.headers.authorization;
const token = extractToken(authHeader);
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch (error) {
res.status(401).json({ error: 'Authentication failed', message: error.message });
}
});This handles common prefix formats in Authorization headers.
Ensure the token is a single-line string without any whitespace:
function sanitizeToken(token) {
// Remove all whitespace, newlines, and carriage returns
const sanitized = token
.replace(/\s+/g, '') // Remove all whitespace
.replace(/\n/g, '') // Remove newlines
.replace(/\r/g, '') // Remove carriage returns
.trim();
console.log('Original length:', token.length);
console.log('Sanitized length:', sanitized.length);
if (token.length !== sanitized.length) {
console.warn('Token contained whitespace characters');
}
return sanitized;
}
// Apply sanitization
const rawToken = req.headers.authorization?.replace('Bearer ', '');
const cleanToken = sanitizeToken(rawToken);
const decoded = jwt.verify(cleanToken, process.env.JWT_SECRET);This removes formatting characters that can break JWT parsing.
Verify the token generation process is creating valid JWTs:
const jwt = require('jsonwebtoken');
// Token generation
function generateToken(userId) {
const payload = {
userId: userId,
iat: Math.floor(Date.now() / 1000), // Issued at
};
const secret = process.env.JWT_SECRET;
if (!secret) {
throw new Error('JWT_SECRET environment variable not set');
}
const token = jwt.sign(payload, secret, {
expiresIn: '24h',
algorithm: 'HS256',
});
console.log('Generated token:', token.substring(0, 30) + '...');
console.log('Token parts:', token.split('.').length);
// Immediately verify the generated token
try {
const verified = jwt.verify(token, secret);
console.log('Token generation verified successfully');
} catch (error) {
console.error('Generated token is invalid:', error.message);
}
return token;
}
// Usage
const token = generateToken('user123');
res.json({ token });This ensures tokens are created correctly before they're sent to clients.
Use the jwt.io debugger to inspect and validate your token:
1. Copy the token string from your logs or browser
2. Go to https://jwt.io/
3. Paste the token in the "Encoded" section
4. Check if the debugger shows "Invalid Token" in red
5. If invalid, inspect which part is causing issues:
- "Invalid signature" means the token structure is valid but signature doesn't match
- "Invalid token" means the structure itself is malformed
6. Verify the decoded header and payload appear correctly
// Debug token components
function debugToken(token) {
const parts = token.split('.');
console.log('Number of parts:', parts.length);
parts.forEach((part, index) => {
const names = ['Header', 'Payload', 'Signature'];
console.log(`\n${names[index]}:`);
console.log('Raw:', part.substring(0, 50) + '...');
console.log('Length:', part.length);
if (index < 2) { // Don't try to decode signature
try {
const decoded = Buffer.from(part, 'base64url').toString('utf8');
console.log('Decoded:', decoded);
} catch (error) {
console.error('Failed to decode:', error.message);
}
}
});
}
// Usage
debugToken(token);This manual inspection helps identify structural problems.
Distinguish between different JWT error types for better debugging:
const jwt = require('jsonwebtoken');
function verifyTokenWithErrorHandling(token, secret) {
try {
const decoded = jwt.verify(token, secret);
return { success: true, data: decoded };
} catch (error) {
// Handle specific JWT error types
if (error.name === 'JsonWebTokenError') {
if (error.message === 'invalid token') {
return {
success: false,
error: 'INVALID_TOKEN',
message: 'Token structure is malformed or corrupted',
details: error.message,
};
} else if (error.message === 'jwt malformed') {
return {
success: false,
error: 'MALFORMED_TOKEN',
message: 'Token does not have three parts',
details: error.message,
};
} else if (error.message.includes('invalid signature')) {
return {
success: false,
error: 'INVALID_SIGNATURE',
message: 'Token signature verification failed',
details: error.message,
};
}
} else if (error.name === 'TokenExpiredError') {
return {
success: false,
error: 'TOKEN_EXPIRED',
message: 'Token has expired',
expiredAt: error.expiredAt,
};
}
return {
success: false,
error: 'UNKNOWN_ERROR',
message: error.message,
};
}
}
// Express middleware
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
const result = verifyTokenWithErrorHandling(token, process.env.JWT_SECRET);
if (!result.success) {
console.error('JWT verification failed:', result);
return res.status(401).json(result);
}
req.user = result.data;
next();
}This provides detailed error context for debugging.
Base64URL vs Base64 Encoding: JWTs use Base64URL encoding (RFC 4648 Section 5), not standard Base64. Base64URL replaces '+' with '-', '/' with '_', and omits padding '=' characters. If you manually construct or modify tokens, ensure you use the correct encoding.
Token Storage and Transmission: When storing tokens in databases or sending via HTTP headers, ensure no additional encoding/escaping is applied. Common issues include: storing tokens with surrounding quotes in databases, double-encoding tokens, or URL-encoding tokens when they should remain as-is.
Secret Key Consistency: The JWT_SECRET used in jwt.verify() must exactly match the secret used in jwt.sign(). Even minor differences (trailing spaces, quotes, different environment variables) will cause verification to fail. Use process.env directly without additional parsing.
Algorithm Mismatches: If tokens are generated with one algorithm (e.g., RS256 using public/private keys) but verified with another (e.g., HS256 expecting a shared secret), you'll get parsing errors. Always specify the algorithm explicitly in both sign() and verify() options.
Debugging Production Issues: In production, tokens may be corrupted by: CDN/proxy modifications, browser extensions, CORS preflight handling, or mobile app HTTP libraries. Log token lengths and first/last characters (not full tokens for security) to identify truncation or corruption patterns.
Security Considerations: Never log complete tokens in production as they grant authentication access. Instead, log only metadata (token length, expiration time, user ID from payload after successful verification). When debugging "invalid token" errors, use token structure validation without exposing sensitive data.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams