Back to blog
July 15, 2026

JWT to PASETO v4 Migration Guide

A complete migration blueprint for developers transitioning from JWT to PASETO v4. Learn how to replace symmetric HS256 and asymmetric RS256 implementations with secure-by-default versioned tokens.

JWT to PASETO v4 Migration Guide

If you are maintaining a modern authentication pipeline, you have likely encountered the historical liabilities of the JSON Web Token (JWT) specification—most notably, algorithm agility, which trusts the client-provided header to define how the server should verify the signature.

Migrating your authentication layer to PASETO (Platform-Agnostic Security Tokens) eliminates algorithm confusion and signature downgrade vulnerabilities by design. Rather than relying on configuration flags, PASETO v4 uses version-locked cryptographic protocols.

This guide provides a step-by-step code blueprint for migrating your authentication services from JWT to PASETO v4 in a Node.js environment.


The Migration Strategy: Side-by-Side Blueprint

A successful migration involves three distinct steps:

  1. Mapping your existing JWT claims to the PASETO payload.
  2. Replacing symmetric JWT (HS256) with symmetric PASETO (v4.local).
  3. Replacing asymmetric JWT (RS256) with asymmetric PASETO (v4.public).

Step 1: Mapping the Claims Payload

PASETO payloads are standard JSON objects, just like JWT claims. Most registered claims map 1-to-1:

JWT ClaimPASETO ClaimDescription
subsubSubject (e.g., User ID)
ississIssuer (e.g., Auth Server)
audaudAudience (e.g., client app)
expexpExpiration Time (ISO 8601 string or Unix timestamp)
iatiatIssued At
nbfnbfNot Before

Note: While JWT expiration times are represented strictly as Unix timestamps (seconds since epoch), PASETO libraries natively support both Unix timestamps and standard ISO 8601 strings (e.g., "2026-07-06T20:18:31Z").


Step 2: Migrating Symmetric Tokens (HS256v4.local)

Symmetric tokens use a single shared key. The main difference is key length: while HS256 can accept arbitrary string passwords (often leading developers to use weak secrets), v4.local strictly requires a secure, cryptographically random 32-byte (256-bit) key.

Before: HS256 JWT Code

import jwt from 'jsonwebtoken';

const SHARED_SECRET = 'my-legacy-secret-string';

// 1. Issuing
const token = jwt.sign({ sub: 'user_123' }, SHARED_SECRET, { expiresIn: '2h' });

// 2. Verifying
try {
  const claims = jwt.verify(token, SHARED_SECRET);
} catch (err) {
  console.error('Invalid signature or expired');
}

After: v4.local PASETO Code

import { V4 } from 'paseto';

// Enforce a strict 32-byte Buffer key
const SECRET_KEY = Buffer.from(process.env.PASETO_SECRET_KEY, 'base64');

// 1. Issuing
const token = await V4.encrypt({ sub: 'user_123' }, SECRET_KEY, { expiresIn: '2h' });

// 2. Verifying (Decrypting)
try {
  const claims = await V4.decrypt(token, SECRET_KEY);
} catch (err) {
  console.error('Decryption failed. Token is invalid or modified.');
}

Step 3: Migrating Asymmetric Tokens (RS256v4.public)

If you use RS256, ES256, or EdDSA with JWT, you verify tokens using public keys. PASETO v4 asymmetric tokens (v4.public) enforce Ed25519 signatures, which offer superior performance and security margins compared to traditional RSA.

Before: RS256 JWT Code

import jwt from 'jsonwebtoken';
import fs from 'fs';

const PRIVATE_KEY = fs.readFileSync('private_rsa.pem');
const PUBLIC_KEY = fs.readFileSync('public_rsa.pem');

// 1. Issuing
const token = jwt.sign({ sub: 'user_123' }, PRIVATE_KEY, { 
  algorithm: 'RS256', 
  expiresIn: '1h' 
});

// 2. Verifying
try {
  const claims = jwt.verify(token, PUBLIC_KEY, { algorithms: ['RS256'] });
} catch (err) {
  console.error('RS256 verification failed');
}

After: v4.public PASETO Code

import { V4 } from 'paseto';

const PRIVATE_KEY_PEM = process.env.ED25519_PRIVATE_KEY;
const PUBLIC_KEY_PEM = process.env.ED25519_PUBLIC_KEY;

// 1. Issuing
const token = await V4.sign({ sub: 'user_123' }, PRIVATE_KEY_PEM, { 
  expiresIn: '1h' 
});

// 2. Verifying
try {
  const claims = await V4.verify(token, PUBLIC_KEY_PEM);
} catch (err) {
  console.error('Ed25519 verification failed');
}

Testing Your PASETO Migration Offline

As you transition your authentication flow, you must inspect your generated tokens during development to verify that claims, scopes, and expiration windows are mapped correctly.

Developer Warning: Never paste active production tokens or private keys into online debuggers, as your credentials can be logged on their servers.

FmtDev provides a 100% Client-Side PASETO Decoder that parses headers, payloads, and footers locally in your browser's memory without sending any data over the network.

Interactive Example
Local Execution
v4.public.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6IjIwMjYtMDctMDZUMjA6MTg6MzFaIn1fZm9vYmFyX3NpZ25hdHVyZV9nb2VzX2hlcmU

Clicking will load this data into the tool locally.


Conclusion and Next Steps

Migrating from JWT to PASETO v4 removes an entire class of security vulnerabilities by eliminating algorithm negotiation. The transition is straightforward: generate your cryptographic keys, swap your libraries, and enforce v4 in your middlewares.

Related Tool

Ready to use the Our Secure Tool tool? All execution is 100% local.

Open Our Secure Tool