← All Guides

JWT vs JWE: Understanding JSON Web Encryption

Learn the differences between signed tokens (JWT) and encrypted tokens (JWE), when to use each, and implementation considerations.

The Fundamental Difference

While JWT (JSON Web Tokens) and JWE (JSON Web Encryption) are both part of the JOSE (JSON Object Signing and Encryption) framework, they serve different security purposes:

JWT (JSON Web Tokens)

  • Purpose: Authentication & Authorization
  • Security: Integrity (signed, not encrypted)
  • Readability: Base64-encoded payload (readable)
  • Use case: Identity claims, permissions

JWE (JSON Web Encryption)

  • Purpose: Confidentiality
  • Security: Encrypted payload (confidential)
  • Readability: Encrypted payload (unreadable)
  • Use case: Sensitive data transport

Key Point: JWT provides integrity (you know it hasn't been tampered with) but not confidentiality (anyone can read the payload). JWE provides both integrity and confidentiality by encrypting the entire payload.

JWT Structure & Security

Standard JWT Format

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Three parts separated by dots:

  • Header - Algorithm and token type (Base64URL)
  • Payload - Claims and data (Base64URL)
  • Signature - Cryptographic signature

JWT Security Properties

  • Integrity: Signature prevents tampering
  • Authenticity: Signature verifies the issuer
  • No Confidentiality: Payload is Base64-encoded (not encrypted)
// Anyone can decode the payload
const payload = JSON.parse(atob('eyJzdWIiOiIxMjM0...'));
console.log(payload.name); // "John Doe" - visible to anyone!

JWE Structure & Security

JWE Format

eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.
OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe...
48V1_ALb6US04U3b.
5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji...
XFBoag

Five parts separated by dots:

  • Header - Encryption algorithm and parameters
  • Encrypted Key - Encrypted content encryption key
  • Initialization Vector - Random IV for encryption
  • Ciphertext - Encrypted payload
  • Authentication Tag - Integrity verification

JWE Security Properties

  • Confidentiality: Payload is encrypted and unreadable
  • Integrity: Authentication tag prevents tampering
  • Authenticity: Verifies the encryptor

When to Use JWT vs JWE

Use JWT When:

  • Identity tokens: User authentication and basic claims
  • Public information: User roles, permissions, non-sensitive data
  • Performance priority: Lower overhead than encryption
  • Stateless authentication: Microservices, APIs
  • Client-side processing: JavaScript can read claims without server calls

Example use case: User authentication in a React app where you need to check user roles client-side to show/hide UI elements.

Use JWE When:

  • Sensitive data: Personal information, financial data
  • Compliance requirements: GDPR, HIPAA, PCI-DSS
  • Zero-trust networks: Data transits untrusted infrastructure
  • Temporary credentials: API keys, temporary passwords
  • Cross-domain data sharing: Encrypted data exchange

Example use case: Passing encrypted user data between microservices where intermediate proxies or load balancers shouldn't see the content.

Implementation Examples

Creating a JWT

const jwt = require('jsonwebtoken');

// Create a standard JWT
const token = jwt.sign(
  { 
    sub: '1234567890',
    name: 'John Doe',
    role: 'admin',
    exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour
  },
  'your-secret-key',
  { algorithm: 'HS256' }
);

// Token payload is readable by anyone
console.log(jwt.decode(token)); // Shows payload without verification

Creating a JWE

const jose = require('jose');

async function createJWE() {
  const secret = new TextEncoder().encode('your-256-bit-secret');
  
  const jwt = await new jose.EncryptJWT({
    sub: '1234567890',
    sensitive_data: 'confidential information',
    ssn: '123-45-6789'
  })
    .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
    .setIssuedAt()
    .setExpirationTime('1h')
    .encrypt(secret);
  
  return jwt; // Encrypted - payload not readable
}

// Decryption requires the secret
async function decryptJWE(token, secret) {
  const { payload } = await jose.jwtDecrypt(token, secret);
  return payload;
}

Hybrid Approach: Nested JWT

You can combine both by creating a JWT and then encrypting it with JWE:

async function createNestedToken() {
  // Step 1: Create a signed JWT
  const innerJWT = jwt.sign(
    { sub: '123', role: 'admin', sensitive: 'data' },
    'signing-secret',
    { algorithm: 'HS256' }
  );
  
  // Step 2: Encrypt the JWT with JWE
  const encryptedJWT = await new jose.EncryptJWT({ jwt: innerJWT })
    .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' })
    .encrypt(encryptionSecret);
  
  return encryptedJWT; // Both signed AND encrypted
}

Algorithm Choices

JWT Signing Algorithms

  • HS256 - HMAC with SHA-256 (shared secret)
  • RS256 - RSA with SHA-256 (public/private key)
  • ES256 - ECDSA with P-256 (recommended for new projects)

JWE Encryption Algorithms

Key Management:

  • dir - Direct use of shared symmetric key
  • RSA-OAEP - RSA with OAEP padding
  • A256KW - AES Key Wrap with 256-bit key

Content Encryption:

  • A256GCM - AES-256 in GCM mode (recommended)
  • A256CBC-HS512 - AES-256-CBC with HMAC-SHA-512

Performance Considerations

Computational Overhead

  • JWT (HS256): Very fast - simple HMAC operation
  • JWT (RS256): Moderate - RSA signature verification
  • JWE (A256GCM): Higher - encryption/decryption overhead
  • Nested JWT in JWE: Highest - both signing and encryption

Size Comparison

  • JWT: ~200-400 bytes for typical claims
  • JWE: ~300-600 bytes (includes encryption overhead)
  • Nested: ~400-700 bytes (largest footprint)

Optimization tip: For high-throughput APIs, use JWT for non-sensitive data and JWE only when confidentiality is required. Consider caching decrypted JWE payloads in memory for the token lifetime.

Security Best Practices

JWT Security

  • Use strong signing keys (256+ bits for HMAC)
  • Always validate signatures before trusting payload
  • Set appropriate expiration times
  • Never store sensitive data in JWT payloads
  • Validate all standard claims (exp, iss, aud)

JWE Security

  • Use authenticated encryption modes (GCM, CCM)
  • Generate unique initialization vectors for each encryption
  • Protect encryption keys with the same rigor as signing keys
  • Consider key rotation for long-term deployments
  • Validate authentication tags before processing plaintext

Common Pitfalls

  • Using JWT for sensitive data - Remember: JWT payloads are readable
  • Algorithm confusion - Always specify allowed algorithms
  • Key reuse - Don't use the same key for signing and encryption
  • Missing validation - Verify tokens before trusting any claims
  • Ignoring expiration - Both JWT and JWE should have time limits

Decision Framework

Quick Decision Tree

Does your payload contain sensitive data?
  • ✅ Yes → Use JWE
  • ❌ No → Continue...
Do you need client-side access to claims?
  • ✅ Yes → Use JWT
  • ❌ No → Continue...
Is maximum performance critical?
  • ✅ Yes → Use JWT
  • ❌ No → Consider JWE for defense in depth

Hybrid Scenarios

  • Public claims in JWT + sensitive data in JWE: Separate tokens for different purposes
  • JWT in JWE: Sign first, encrypt second for both integrity and confidentiality
  • Different algorithms per environment: JWE in production, JWT in development

Related Tools & Guides