How to Prevent Replay Attacks with JWTs: JWS vs JWE and Fingerprint Validation in Node.js

In this post, we’ll explore what replay attacks are, how JWS and JWE differ, and how to generate + validate session fingerprints using Node.js to stop these attacks in real-time.

What is a Replay Attack?

A replay attack occurs when an attacker captures a valid token and reuses it to impersonate a user, even though the token is technically still valid and signed.

Real-World Example:

  1. User logs in and receives a JWT

  2. Attacker steals the token (via XSS, insecure storage, etc.)

  3. Attacker replays the token from a different browser, device, or network

  4. Server trusts the token — because the signature is valid!

JWS vs JWE: What's the Difference?

SpecPurposeProtectsCan Prevent Replay?
JWS (JSON Web Signature)Verifies authenticityIntegrity (not confidentiality)❌ No
JWE (JSON Web Encryption)Encrypts contentConfidentiality & integrity❌ No

Key Insight:

  • JWS prevents tampering, not reuse.

  • JWE protects payload visibility, but replay is still possible if the attacker has the token.

Replay protection needs to come from external context like where the token is used (IP, device, session).

Strategy: Fingerprint the Request

We can bind tokens to users more securely by:

  • Capturing unique request traits (like IP and User-Agent)

  • Creating a SHA-256 fingerprint

  • Embedding that fingerprint into the JWT during issuance

  • Validating the fingerprint with every incoming request

Node.js App: Generating and Validating Fingerprints

Dependencies

npm install express jsonwebtoken crypto

App Code

const express = require('express');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');

const app = express();
app.use(express.json());

const SECRET = 'super_secret_key';
const TOKEN_EXPIRY = '5m';

// Generate fingerprint from IP + User-Agent
function generateFingerprint(req) {
  const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
  const userAgent = req.headers['user-agent'] || '';
  const raw = `${ip}:${userAgent}`;
  return crypto.createHash('sha256').update(raw).digest('hex');
}

// Login endpoint
app.post('/login', (req, res) => {
  const fingerprint = generateFingerprint(req);

  const payload = {
    sub: 'user123',
    fp: fingerprint, // Embed fingerprint in token
  };

  const token = jwt.sign(payload, SECRET, { expiresIn: TOKEN_EXPIRY });
  res.json({ token });
});

// Protected route
app.get('/secure', (req, res) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader?.split(' ')[1];

  if (!token) return res.status(401).json({ error: 'No token provided' });

  try {
    const decoded = jwt.verify(token, SECRET);
    const requestFp = generateFingerprint(req);

    if (decoded.fp !== requestFp) {
      return res.status(403).json({ error: 'Replay attack detected (fingerprint mismatch)' });
    }

    res.json({ message: 'Secure access granted', user: decoded.sub });
  } catch (err) {
    res.status(401).json({ error: 'Invalid or expired token' });
  }
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Breakdown

🔧 Component📋 Explanation
FingerprintCombines IP + User-Agent and hashes it
Login routeGenerates fingerprint and embeds it in JWT
Secure routeRecomputes fingerprint and compares it with token's fp field
MismatchRequest is blocked — possible token misuse detected

Replay Protection Flow

sequenceDiagram
  participant Client
  participant Server

  Client->>Server: POST /login (IP + User-Agent)
  Server->>Server: Generate fingerprint hash
  Server-->>Client: JWT with fingerprint

  Client->>Server: GET /secure (JWT included)
  Server->>Server: Recreate fingerprint from request
  alt Match
    Server-->>Client: ✅ Access granted
  else Mismatch
    Server-->>Client: ❌ Replay detected
  end

Best Practices for Production

PracticeWhy It Helps
Use https everywherePrevents sniffing tokens via network
Store refresh tokens in HttpOnly cookiesPrevents token theft via XSS
Add jti to track usage or blacklist tokensEnables invalidation control
Use short access token lifetimes (e.g., 5 min)Reduces reuse window
Use fingerprint checks only for sensitive endpointsDon’t break valid multi-device access

Summary

JWTs solve authentication — but not everything. To prevent replay attacks, you need to:

  • Bind tokens to a fingerprint

  • Validate that fingerprint on every sensitive action

  • Combine with best practices like token rotation, refresh flows, and session handling

A signed token isn’t enough. Make sure it’s also being used by the right device, under the right conditions.

Call to Action

Have you implemented fingerprinting or anti-replay measures in your own apps? Got a war story or tip?
Let’s talk in the comments 👇

0
Subscribe to my newsletter

Read articles from Faiz Ahmed Farooqui directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Faiz Ahmed Farooqui
Faiz Ahmed Farooqui

Principal Technical Consultant at GeekyAnts. Bootstrapping our own Data Centre services. I lead the development and management of innovative software products and frameworks at GeekyAnts, leveraging a wide range of technologies including OpenStack, Postgres, MySQL, GraphQL, Docker, Redis, API Gateway, Dapr, NodeJS, NextJS, and Laravel (PHP). With over 9 years of hands-on experience, I specialize in agile software development, CI/CD implementation, security, scaling, design, architecture, and cloud infrastructure. My expertise extends to Metal as a Service (MaaS), Unattended OS Installation, OpenStack Cloud, Data Centre Automation & Management, and proficiency in utilizing tools like OpenNebula, Firecracker, FirecrackerContainerD, Qemu, and OpenVSwitch. I guide and mentor a team of engineers, ensuring we meet our goals while fostering strong relationships with internal and external stakeholders. I contribute to various open-source projects on GitHub and share industry and technology insights on my blog at blog.faizahmed.in. I hold an Engineer's Degree in Computer Science and Engineering from Raj Kumar Goel Engineering College and have multiple relevant certifications showcased on my LinkedIn skill badges.