Skip to content

JWT JOSE — JSON Object Signing and Encryption Standards for Advanced Token Security

DodaTech Updated 2026-06-28 4 min read

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

  1. What does JWE provide that JWS does not?
  2. Why sign before encrypting in nested JWT?
  3. What information is in a JWK besides the public key?
  4. What algorithms does JWA register?
  5. How do clients select the right key from a JWKS?

Answers:

  1. JWE provides confidentiality (encryption) so the payload is not readable without the private key.
  2. Signing first ensures the original signer's integrity is preserved through encryption. If encrypted first, the signature can be stripped.
  3. JWK includes key type, algorithm, key ID (kid), usage (use), and the key material.
  4. JWA registers algorithm names for signing (RS256, ES256), encryption (RSA-OAEP, A256GCM), and key management.
  5. 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 is the difference between JWS and JWT?

: JWT is built on JWS. A JWT is a JWS with specific claims (sub, exp, iat, etc.).

When should I use JWE?

: When your token contains PII, financial data, or any claims that should not be visible in transit.

Can I use JWE without JWS?

: Yes, but then you lose integrity protection. Someone with the encryption key could modify claims.

What is the `typ` header in JWT?

: It declares the token type. JWT uses typ: JWT, JOSE uses typ: JOSE.

How does JWKS key rotation work?

: Add a new key to the JWKS with a new kid. Issuers start signing with the new key. Old keys remain for verification until all tokens signed with them expire.

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