11 min read

Guide · Developer security

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)

PurposeAuthentication & Authorization
SecurityIntegrity (signed, not encrypted)
ReadabilityBase64-encoded payload (readable)
Use caseIdentity claims, permissions

JWE (JSON Web Encryption)

PurposeConfidentiality
SecurityEncrypted payload (confidential)
ReadabilityEncrypted payload (unreadable)
Use caseSensitive 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

JWT token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Three parts separated by dots:

HeaderAlgorithm and token type (Base64URL)
PayloadClaims and data (Base64URL)
SignatureCryptographic signature

JWT Security Properties

  • Integrity: Signature prevents tampering
  • Authenticity: Signature verifies the issuer
  • No Confidentiality: Payload is Base64-encoded (not encrypted)
Node.js
// 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

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

Five parts separated by dots:

HeaderEncryption algorithm and parameters
Encrypted KeyEncrypted content encryption key
Initialization VectorRandom IV for encryption
CiphertextEncrypted payload
Authentication TagIntegrity 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

Node.js
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

Node.js
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:

Node.js
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

HS256HMAC with SHA-256 (shared secret)
RS256RSA with SHA-256 (public/private key)
ES256ECDSA with P-256 (recommended for new projects)

JWE Encryption Algorithms

Key Management:

dirDirect use of shared symmetric key
RSA-OAEPRSA with OAEP padding
A256KWAES Key Wrap with 256-bit key

Content Encryption:

A256GCMAES-256 in GCM mode (recommended)
A256CBC-HS512AES-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 JWEHighest - 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

Avoid:
  • 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