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 & AuthorizationSecurityIntegrity (signed, not encrypted)ReadabilityBase64-encoded payload (readable)Use caseIdentity claims, permissionsJWE (JSON Web Encryption)
PurposeConfidentialitySecurityEncrypted payload (confidential)ReadabilityEncrypted payload (unreadable)Use caseSensitive data transportKey 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_adQssw5cThree parts separated by dots:
HeaderAlgorithm and token type (Base64URL)PayloadClaims and data (Base64URL)SignatureCryptographic signatureJWT 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...
XFBoagFive parts separated by dots:
HeaderEncryption algorithm and parametersEncrypted KeyEncrypted content encryption keyInitialization VectorRandom IV for encryptionCiphertextEncrypted payloadAuthentication TagIntegrity verificationJWE 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 verificationCreating 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 keyRSA-OAEPRSA with OAEP paddingA256KWAES Key Wrap with 256-bit keyContent Encryption:
A256GCMAES-256 in GCM mode (recommended)A256CBC-HS512AES-256-CBC with HMAC-SHA-512Performance Considerations
Computational Overhead
JWT (HS256)Very fast - simple HMAC operationJWT (RS256)Moderate - RSA signature verificationJWE (A256GCM)Higher - encryption/decryption overheadNested JWT in JWEHighest - both signing and encryptionSize Comparison
JWT~200-400 bytes for typical claimsJWE~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