jwt-decode vs jwt.verify: When to Use Which
I once reviewed a pull request where the developer used jwt-decode to authenticate API requests. When I asked why they were not verifying the signature, they said: "I am just reading the user ID from the token. Why do I need to verify it?"
This is one of the most common JWT misconceptions. And it leads to vulnerabilities that are trivially exploitable.
The Fundamental Difference
// jwt-decode: reads the payload WITHOUT verifying the signature
const decoded = jwtDecode(token);
// Anyone can craft a token with any payload
// jwt.verify: decodes AND verifies the signature
const verified = jwt.verify(token, secretOrPublicKey);
// Only tokens signed by the expected issuer are accepted
On Stack Overflow, the question about this exact difference has 47,000 views. The top answer (800+ upvotes) states: "Decode without verify is like reading an envelope's return address without checking if it was actually sent by that person."
The Exploit
// What happens when you decode without verification:
const token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VySWQiOjEsInJvbGUiOiJhZG1pbiJ9.";
// header (alg: none) ........ payload (admin) ........ no signature
const decoded = jwtDecode(token);
// { userId: 1, role: "admin" }
// If your authorization logic trusts this:
if (decoded.role === "admin") {
grantAdminAccess(); // ← Attacker gets admin access
}
Anyone can create a JWT with any payload using the alg: "none" header or by changing the algorithm from RS256 to HS256 and signing with the public key.
When jwt-decode Is Actually Safe
There is exactly one scenario where jwt-decode is safe: client-side display of non-sensitive claims.
// ✅ Safe: client-side display only
import { jwtDecode } from "jwt-decode";
function UserProfile({ token }) {
const decoded = jwtDecode(token); // No verification needed here
return (
<div>
<h2>Welcome, {decoded.name}</h2>
{/* Display-only. No security decisions based on this data */}
</div>
);
}
But even in this case, remember that the user can modify the decoded values in the browser using DevTools. Do not use the decoded data for any security-critical display (like "You have admin access") without verifying first.
The Production Pattern
// Authentication middleware — ALWAYS verify
function authenticateToken(req, res, next) {
const authHeader = req.headers.authorization;
const token = authHeader && authHeader.split(" ")[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: "No token provided" });
}
try {
// Verify signature AND check expiration
const decoded = jwt.verify(token, process.env.JWT_PUBLIC_KEY, {
algorithms: ["RS256"],
maxAge: "15m"
});
// Attach verified claims to request
req.user = {
id: decoded.sub,
roles: decoded.roles
};
next();
} catch (err) {
if (err.name === "TokenExpiredError") {
return res.status(401).json({ error: "Token expired" });
}
if (err.name === "JsonWebTokenError") {
return res.status(403).json({ error: "Invalid token" });
}
return res.status(500).json({ error: "Authentication error" });
}
}
A Reddit thread about production JWT mistakes has over 300 comments, with the most frequent mistake being "decoding without verifying" in server-side code.
Related Searches
- jwt decode vs verify
- jwt-decode npm
- jsonwebtoken verify
- jwt without verification vulnerability
- jwt algorithm none attack
- jwt authentication best practices
- jwt verify server side
- decode jwt without key
- is jwt decode safe
- jwt token validation
Frequently Asked Questions
Can I use jwt-decode on the server?
Only for non-security purposes like logging or displaying the token's contents for debugging. Never make authorization decisions based on unverified JWT claims on the server.
What happens if I call jwt.verify without a key?
The function throws an error. jwt.verify() always requires a secret or public key. If you want to decode without verification, use jwtDecode() from the jwt-decode package.
How do I verify a JWT on the frontend?
You generally do not need to. The frontend should treat the JWT as an opaque string that the server issued. If you must verify client-side (for debugging or claim inspection), use jwt-decode but do not make security decisions based on the decoded data.
What is the performance cost of jwt.verify vs jwtDecode?
jwtDecode is nearly instant (base64 decode only). jwt.verify requires cryptographic signature verification, which takes 0.1-1ms depending on the algorithm. RS256/ES256 (asymmetric) is slower than HS256 (symmetric). The performance difference is negligible for most applications.
Can I decode a JWT without knowing the secret?
Yes. JWT payloads are only base64-encoded, not encrypted. Anyone can decode the payload. The signature verification is what prevents tampering. This is why you should never store sensitive data in a JWT payload.
Final Thoughts
The rule is simple: verify on the server, decode on the client. Any server-side code that makes authorization decisions must verify the JWT signature first. Any client-side code that displays user information from the token can decode without verifying, but should not make security decisions based on the decoded data.