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_adQssw5cThree 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...
XFBoagFive 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 verificationCreating 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 keyRSA-OAEP- RSA with OAEP paddingA256KW- 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
- ✅ Yes → Use JWE
- ❌ No → Continue...
- ✅ Yes → Use JWT
- ❌ No → Continue...
- ✅ 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