JWT JOSE — JSON Object Signing and Encryption Standards for Advanced Token Security
In this tutorial, you will learn about JWT JOSE. We cover key concepts, practical examples, and best practices to help you master this topic.
JOSE (JSON Object Signing and Encryption) is the standards framework underlying JWTs, defining JWS (signing), JWE (encryption), JWK (key format), and JWA (algorithm registry) for end-to-end token security.
What You'll Learn
- JWS: signing JSON payloads with various algorithms
- JWE: encrypting JSON payloads for confidentiality
- JWK: representing keys as JSON objects
- JWA: registered algorithm identifiers
- Nested JWT: signing then encrypting for integrity + confidentiality
Why It Matters
Standard JWTs only sign the payload, leaving it readable. JWE adds encryption for sensitive claims. JWK enables key discovery. Nested JWT (sign then encrypt) provides both integrity and confidentiality. DodaTech uses JWE for token claims containing customer PII, ensuring Compliance with data protection regulations.
Real-World Use
A healthcare API uses nested JWTs: the inner token is signed with ES256 for integrity, then the entire token is encrypted with RSA-OAEP for confidentiality. Patient IDs, diagnosis codes, and authorization scopes are never visible in plaintext.
flowchart LR
A["JWT Claims
{sub, roles, scope}"] --> B["JWS Sign
(integrity)"]
B --> C["JWS Token
(readable)"]
C --> D["JWE Encrypt
(confidentiality)"]
D --> E["Nested JWT
(JWS + JWE)"]
E --> F["Transmit over network"]
F --> G["JWE Decrypt"]
G --> H["JWS Verify"]
H --> I["Claims extracted"]
Code Examples
Example 1: Creating a JWS (Signed JWT)
from jwcrypto import jwk, jwt
from jwcrypto.common import json_encode
import json
# Generate key
key = jwk.JWK.generate(kty='EC', crv='P-256', use='sig')
# Create signed JWT
claims = {
'sub': 'user_123',
'roles': ['analyst'],
'iat': int(datetime.now(timezone.utc).timestamp()),
'exp': int(datetime.now(timezone.utc).timestamp()) + 900
}
token = jwt.JWT(header={'alg': 'ES256', 'typ': 'JWT'},
claims=claims)
token.make_signed_token(key)
signed = token.serialize()
print(f"JWS Token: {signed[:60]}...")
# Output: JWS Token: eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...
# Verify JWS
verified = jwt.JWT()
verified.deserialize(signed, key)
print(f"Verified claims: {verified.claims['sub']}")
# Output: Verified claims: user_123
Example 2: Creating a JWE (Encrypted JWT)
from jwcrypto import jwk, jwt
# Generate encryption key pair
enc_key = jwk.JWK.generate(kty='RSA', size=2048, use='enc')
# Create encrypted JWT
claims = {
'patient_id': 'P-98765',
'diagnosis': 'MALWARE-INFECTION',
'severity': 'critical',
'hipaa_restricted': True
}
token = jwt.JWT(header={
'alg': 'RSA-OAEP',
'enc': 'A256GCM',
'typ': 'JWE'
}, claims=claims)
token.make_encrypted_token(enc_key)
encrypted = token.serialize()
print(f"JWE Token length: {len(encrypted)} chars")
# Output: JWE Token length: 1524 chars
# Decrypt JWE (only holder of private key)
dec_token = jwt.JWT()
dec_token.deserialize(encrypted, enc_key)
print(f"Decrypted claims: {dec_token.claims}")
# Output: Decrypted claims: {"patient_id": "P-98765", ...}
Example 3: JWK Set (JWKS) for Key Discovery
from jwcrypto import jwk
import json
# Create JWK Set with multiple keys
key1 = jwk.JWK.generate(kty='EC', crv='P-256', use='sig', kid='key-1')
key2 = jwk.JWK.generate(kty='EC', crv='P-384', use='sig', kid='key-2')
jwks = {
'keys': []
}
for key in [key1, key2]:
jwks['keys'].append(json.loads(key.export(public_only=True)))
print(json.dumps(jwks, indent=2))
# Output:
# {
# "keys": [
# {"kty": "EC", "crv": "P-256", "kid": "key-1", ...},
# {"kty": "EC", "crv": "P-384", "kid": "key-2", ...}
# ]
# }
# Client resolves key by kid
def get_key_by_kid(jwks, kid):
for key_data in jwks['keys']:
if key_data.get('kid') == kid:
return jwk.JWK(**key_data)
return None
Common Mistakes
1. Signing After Encryption
Always sign first, then encrypt. If you encrypt first, the signature can be stripped and replaced by anyone with the encryption key.
2. Using the Same Key for Signing and Encryption
Signing and encryption keys should be separate per cryptographic hygiene principles.
3. Ignoring Key Rotation
JWKS keys should have an expiration and should be rotated regularly.
4. Over-Encrypting Non-Sensitive Claims
Encryption adds overhead. Only encrypt claims that contain sensitive data.
5. Not Validating Nested JWT Correctly
When using JWS+JWE, verify the inner signature after decryption — never trust outer structures.
Practice Questions
- What does JWE provide that JWS does not?
- Why sign before encrypting in nested JWT?
- What information is in a JWK besides the public key?
- What algorithms does JWA register?
- How do clients select the right key from a JWKS?
Answers:
- JWE provides confidentiality (encryption) so the payload is not readable without the private key.
- Signing first ensures the original signer's integrity is preserved through encryption. If encrypted first, the signature can be stripped.
- JWK includes key type, algorithm, key ID (kid), usage (use), and the key material.
- JWA registers algorithm names for signing (RS256, ES256), encryption (RSA-OAEP, A256GCM), and key management.
- Clients match the
kid(key ID) in the JWT header against keys in the JWKS.
Challenge: Create a nested JWT: sign claims with ES256, then encrypt the signed token with RSA-OAEP+A256GCM. Decrypt and verify on the receiving end. Implement key rotation by serving a JWKS with two keys.
FAQ
What's Next
Apply JOSE standards in your {{< ilink "JWT" "JWT Authentication Service" }}, and explore {{< ilink "JWT" "JWT Best Practices" }} for production configuration guidance.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro