Signing and Verifying Tokens
Objectives
By the end of this lesson, you should be able to:
- Sign a JWT with jsonwebtoken
- Verify a token, and handle an invalid or expired one
- Explain exactly what causes verification to fail
💡 Why this matters: The last lesson decoded a token’s contents without checking whether it was genuine. This lesson covers the actual trust mechanism, verification, that makes a JWT usable for real authentication.
⚠️ A note on verification: every snippet and every output in this lesson was actually run.
Installing jsonwebtoken
npm install jsonwebtoken
Signing a Token
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET || 'dev-only-secret';
const token = jwt.sign(
{ userId: 1, email: 'erin@example.com' },
SECRET,
{ expiresIn: '1h' }
);
console.log(token);
jwt.sign(payload, secret, options), the payload becomes the token’s claims (Lesson 1), the secret is used to generate the signature, expiresIn sets how long the token stays valid. In a real app, SECRET comes from an environment variable (the same pattern used throughout this track), never a hard-coded string.
Verifying a Valid Token
const decoded = jwt.verify(token, SECRET);
console.log(decoded);
{
userId: 1,
email: 'erin@example.com',
iat: 1785865536,
exp: 1785869136
}
jwt.verify() checks the signature and returns the decoded payload if it’s valid, this is the step a route uses to trust a token’s contents (Lesson 3).
Verifying with the Wrong Secret
try {
jwt.verify(token, 'wrong-secret');
} catch (err) {
console.log(err.message);
}
invalid signature
If the server’s secret doesn’t match what the token was signed with, verification fails immediately, this is exactly what stops anyone without the secret from forging a valid token.
Verifying a Tampered Token
try {
jwt.verify(token + 'tampered', SECRET);
} catch (err) {
console.log(err.message);
}
invalid signature
Appending even a single character changes the token, the signature no longer matches, verification catches it the same way it catches a wrong secret, tampering and forgery are the same underlying problem.
Verifying an Expired Token
const expiredToken = jwt.sign({ userId: 1 }, SECRET, { expiresIn: '-1s' });
try {
jwt.verify(expiredToken, SECRET);
} catch (err) {
console.log(err.message);
}
jwt expired
expiresIn: '-1s' creates a token that’s already expired, useful here only to demonstrate the exact error, jwt.verify() checks the exp claim automatically and rejects an expired token, even one with a perfectly valid signature.
Try It
- Sign a token with
jwt.sign(), and verify it successfully withjwt.verify(). - Attempt to verify it with the wrong secret, and confirm the exact error message shown above.
- Attempt to verify a tampered token (append or change a character), and confirm it fails the same way.
- Create an already-expired token with
expiresIn: '-1s', and confirmjwt.verify()rejects it withjwt expired.
Recap
jwt.sign(payload, secret, options)creates a token,jwt.verify(token, secret)checks it and returns the decoded payload if valid.- A wrong secret or a tampered token both fail with
invalid signature, the same underlying protection. - An expired token fails verification even with a correct signature,
jwt.verify()checks theexpclaim automatically.
Next lesson: using this to actually protect an API route, replacing the session check from Module 2 with a token check.