CodingNic

Token-Based Authentication with JWT

Signing and Verifying Tokens

Token-Based Authentication with JWT 15 min read

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

bash
npm install jsonwebtoken

Signing a Token

javascript
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

javascript
const decoded = jwt.verify(token, SECRET);
console.log(decoded);
text
{
  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

javascript
try {
  jwt.verify(token, 'wrong-secret');
} catch (err) {
  console.log(err.message);
}
text
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

javascript
try {
  jwt.verify(token + 'tampered', SECRET);
} catch (err) {
  console.log(err.message);
}
text
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

javascript
const expiredToken = jwt.sign({ userId: 1 }, SECRET, { expiresIn: '-1s' });
try {
  jwt.verify(expiredToken, SECRET);
} catch (err) {
  console.log(err.message);
}
text
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

  1. Sign a token with jwt.sign(), and verify it successfully with jwt.verify().
  2. Attempt to verify it with the wrong secret, and confirm the exact error message shown above.
  3. Attempt to verify a tampered token (append or change a character), and confirm it fails the same way.
  4. Create an already-expired token with expiresIn: '-1s', and confirm jwt.verify() rejects it with jwt 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 the exp claim automatically.

Next lesson: using this to actually protect an API route, replacing the session check from Module 2 with a token check.