By default, JSON Web Tokens (JWTs) are base64url encoded JSON objects signed using a digital signing algorithm thanks to JSON Web Signatures (JWS). JWS assures integrity, authentication, and non-repudiation, but it does not provide you with confidentiality. Anyone can read the payload, which can be an issue if the token holds any sort of sensitive data. So, how do you prevent this? How do you encrypt a JWT?
This is where JSON Web Encryption (JWE) comes in. JWE allows you to encrypt a JWT payload so that only the intended recipient can read it while still providing integrity and authentication checks. Combine this with JWS, and you have an encrypted token suitable for use as an access token in OAuth or an identity token in OpenID Connect.
In this article, you will learn how to protect sensitive data by encrypting your JWTs, how JWE works, and what encryption algorithms are typically available.
JSON Web Encryption (JWE) – encrypted JWTs
Your typical encrypted JWT uses JWE Compact Serialization (as opposed to less popular JWE JSON Serialization) and looks something like the following.
Example JWE
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJraWQiOiIxOGIxY2Y3NThjMWQ0ZWM2YmRhNjU4OTM1N2FiZGQ4NSIsInR5cCI6IkpXVCIsImN0eSI6IkpXVCJ9.gCbxP78o3DgpDTUQbuHniuGgYpATqgGkRGy7paC6hRrz7N7eIa6sAOWDO9Fhnj-c8ocMl4cF4Jb_mv5qRPCh9r57PBqx7jOhMIMPTwJGpjcyBaqtHlZlu1vupY5tQ3Y2jGz1Ti4BnywaeEHPyIPQJtN7F7hIAORzj7IY4sIKkVXtQJZgaKW8pEHq_GCqj8i5aaiM0uJnRG3GOh3livp9Npjv9doqp3gyPa1zjrg2H1RsOGn0j2QMGvtuVfkuNwF-SoPKFECyHOq0ZK1oH2sTO8-JwvHflbIZQr5xWTpS8q7MbUXEuqURtrg0Tj-2z6tdaOLT4b3UeDufK2ar3bBfRD4-nRALtoY0ekcMyGFOS7o1Mxl3hy5sIG-EySyWeuBVy68aDWDpi9qZoQuY1TbxxakjncCOGu_Gh1l1m_mK2l_IdyXCT_GCfzFq4ZTkPZ5eydNBAPZuxBLUb4BrMb5iDdZjT7AgGOlRre_wIRHmmKm8W9nDeQQRmbIXO23JuOw9.BDCarfq2r_Uk8DHNfsNwSQ.4DuQx1cfJXadHnudrVaBss45zxyd6iouuSzZUyOeM4ikF_7hDOgwmaCma-Z97_QZBJ5DzVn9SJhKUTAqpVR3BRGAxJ_HAXU5jaTjXqbvUaxsh7Z5TgZ9eck0FIoe1lkwv51xEvYqqQ_Xojr4MAEmLuME_9ArCK9mNaMADIzOj4VoQtaDP1l26ytocc-oENifBRYGu28LbJLkyQKzyQy6FuAOtWjLM0WCXV7-o_dvj6qfeYHNBD7YBSxyqdgD8dcxMBNd2sK73YsZPHEa0V1-8zz7hm3bH3tZelpwPWScqLLW_SUH586c0FVeI6ggvqzjfLZ_Y6eQibVSdXfOtJBk22QrLsuCXbRK8G1w9t23Pwu8ukUAw4v0l7HeaW_0SJyKSPQANRP83MyFbK7fmzTYaW9TYN2JrKN-PLpd2dIFSm2Ga_EfaCwNJBm4RDMzDNrf-O0AissvYyHb0WaALiCiFCogliYqLzRB6xDb-b4964M.J7WDOFLRRPJ7lLpTfN2mOiXLDg5xtaF-sLQ4mOeN5oc
An encrypted JWT (JWE) has 5 sections, unlike the usual 3 sections found with a signed JWT (JWS). Let’s take a look at each section.
JWE protected header
Just like any other JWT, JWE also includes a header. This header describes the JWE, how it was encrypted, and the media type of the encrypted content (the type of data behind the ciphertext).
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJraWQiOiIxOGIxY2Y3NThjMWQ0ZWM2YmRhNjU4OTM1N2FiZGQ4NSIsInR5cCI6IkpXVCIsImN0eSI6IkpXVCJ9
The recipient needs to be able to read this header so that they know how to decrypt it, which means you cannot encrypt it. So, when you base64url decode this header, you end up with a JSON object.
{
"alg": "RSA-OAEP",
"enc": "A256CBC-HS512",
"kid": "18b1cf758c1d4ec6bda6589357abdd85",
"typ": "JWT",
"cty": "JWT"
}
The type (typ) header refers to the JWE itself; in this case, it’s the default “JWT”, whereas the content type (cty) header refers to what type of data lies behind the encrypted payload. In this example, the encrypted payload contains another JWT, a Nested JWT. You’ll see more on this later.
Hybrid encryption
With JWE, the algorithm (alg) header describes the asymmetric encryption algorithm used to encrypt the Content Encryption Key (CEK), and the key ID (kid) header refers to the public key used to encrypt it.
The encryption algorithm (enc) header describes the symmetric encryption algorithm used to encrypt the content itself with the CEK.
This use of hybrid encryption means you use the faster symmetric encryption to encrypt the token payload, which, in theory, could be of any size, and the limited asymmetric encryption to encrypt the encryption key, which is a fixed size and relatively small. It also means you get the assurances of public-key cryptography, where many people can encrypt with the public key, but only one can decrypt with the private key. This is perfect for distributed systems and protocols such as OAuth, where there is a single token issuer and many token validators.
In this example, the asymmetric encryption algorithm was RSAES OAEP (RSA-OAEP), while the symmetric encryption algorithm was AES-256-CBC using an HMAC-SHA-256 (A256CBC-HS512). Better algorithms are available.
Authenticated encryption
JWE requires the use of authenticated encryption so that the message is both encrypted and integrity protected. Authenticated encryption not only defends against a whole class of attacks against encryption algorithms; it also allows you to protect additional data.
Additional Authenticated Data (AAD) is data you want to protect but do not want to encrypt. Using the JWE header as the AAD protects it from being modified by a malicious party. It allows you to include it in the integrity & authentication checks of authenticated encryption without needing to encrypt it. After all, the token recipient needs to be able to read it in order to understand how to decrypt and validate the token.
Using authenticated encryption produces not only the ciphertext for the message but also an authentication tag, which you use to validate the integrity of the ciphertext and the additional authenticated data. You’ll see this tag again shortly.
JWE Content Encryption Key (CEK)
The CEK is the key used to encrypt the JWE payload. This is the key used during symmetric encryption, itself encrypted using your asymmetric encryption key (RSA-OAEP).
gCbxP78o3DgpDTUQbuHniuGgYpATqgGkRGy7paC6hRrz7N7eIa6sAOWDO9Fhnj-c8ocMl4cF4Jb_mv5qRPCh9r57PBqx7jOhMIMPTwJGpjcyBaqtHlZlu1vupY5tQ3Y2jGz1Ti4BnywaeEHPyIPQJtN7F7hIAORzj7IY4sIKkVXtQJZgaKW8pEHq_GCqj8i5aaiM0uJnRG3GOh3livp9Npjv9doqp3gyPa1zjrg2H1RsOGn0j2QMGvtuVfkuNwF-SoPKFECyHOq0ZK1oH2sTO8-JwvHflbIZQr5xWTpS8q7MbUXEuqURtrg0Tj-2z6tdaOLT4b3UeDufK2ar3bBfRD4-nRALtoY0ekcMyGFOS7o1Mxl3hy5sIG-EySyWeuBVy68aDWDpi9qZoQuY1TbxxakjncCOGu_Gh1l1m_mK2l_IdyXCT_GCfzFq4ZTkPZ5eydNBAPZuxBLUb4BrMb5iDdZjT7AgGOlRre_wIRHmmKm8W9nDeQQRmbIXO23JuOw9
The CEK is different for each token, generated for one-time use. It must never be re-used.
JWE Initialization Vector (IV)
The initialization vector used when encrypting the plaintext. This must be a unique, random value. If your symmetric encryption algorithm does not require an initialization vector, then this should be an empty octet sequence.
BDCarfq2r_Uk8DHNfsNwSQ
JWE payload (the ciphertext)
The encrypted payload which was encrypted using authenticated encryption thanks to your symmetric encryption algorithm (A256CBC-HS512). This used the JWE’s Content Encryption Key (CEK) as the encryption key, along with the JWE initialization vector and the “Additional Authenticated Data” (AAD), which, when using compact serialization, is the encoded JWE protected header.
4DuQx1cfJXadHnudrVaBss45zxyd6iouuSzZUyOeM4ikF_7hDOgwmaCma-Z97_QZBJ5DzVn9SJhKUTAqpVR3BRGAxJ_HAXU5jaTjXqbvUaxsh7Z5TgZ9eck0FIoe1lkwv51xEvYqqQ_Xojr4MAEmLuME_9ArCK9mNaMADIzOj4VoQtaDP1l26ytocc-oENifBRYGu28LbJLkyQKzyQy6FuAOtWjLM0WCXV7-o_dvj6qfeYHNBD7YBSxyqdgD8dcxMBNd2sK73YsZPHEa0V1-8zz7hm3bH3tZelpwPWScqLLW_SUH586c0FVeI6ggvqzjfLZ_Y6eQibVSdXfOtJBk22QrLsuCXbRK8G1w9t23Pwu8ukUAw4v0l7HeaW_0SJyKSPQANRP83MyFbK7fmzTYaW9TYN2JrKN-PLpd2dIFSm2Ga_EfaCwNJBm4RDMzDNrf-O0AissvYyHb0WaALiCiFCogliYqLzRB6xDb-b4964M
Nested JWTs
JWE allows you to encrypt any arbitrary payload; however, a common use case is for the payload to be another JWT. This is known as a Nested JWT.
eyJhbGciOiJFUzI1NiIsImtpZCI6IjUzMzc1ZjliZDIzNjQ1MmZiYWQ3NTZkMDRhYzNiM2UzIiwidHlwIjoiSldUIn0.eyJzdWIiOiI4MTFlNzkwNzQ5YTI0ZDhhOGY3NjZlMWE0NGRjYTI4YSIsImF1ZCI6ImFwaTEiLCJpc3MiOiJodHRwczovL2lkcC5leGFtcGxlLmNvbSIsImV4cCI6MTY2MDMwMjc0MywiaWF0IjoxNjYwMjk5MTQzLCJuYmYiOjE2NjAyOTkxNDN9.gTUsxf15SObpxqUzLXY_kDZtFcoucCMK-GQSgA2tJIq9v6BoztSG7grbc8UPDwT6veeKTO0wRFDWYht3BJLBQQ
While the JWE standard allows for the message to be any arbitrary value and support for Nested JWTs is optional, the main use cases of JWTs require it to be a signed JWT (think of protocols such as OAuth and OpenID Connect).
By using a Nested JWT, you gain the benefits of asymmetric encryption, where many systems can encrypt and only one can decrypt, and the benefits of asymmetric signatures, where only one system can create signatures while many can validate them.
This means that JWE gives you confidentiality, integrity, and authentication, while JWS gives you integrity, authentication, and non-repudiation. JWE alone does not allow you to prove that a trusted token issuer created the JWT, only that it was created by someone who knew the public key. With nested JWTs, you get the benefits of both JWE and JWS.
{
"alg": "ES256",
"kid": "53375f9bd236452fbad756d04ac3b3e3",
"typ": "JWT"
}.
{
"sub": "811e790749a24d8a8f766e1a44dca28a",
"aud": "api1",
"iss": "https://idp.example.com",
"exp": 1660302743,
"iat": 1660299143,
"nbf": 1660299143
}
You must always validate the Nested JWT, including its signature. Never allow unprotected Nested JWTs.
JWE authentication tag
The authentication tag created during authenticated encryption allows the verifier to prove the integrity of the ciphertext and the AAD (the header). This tag is created during encryption and validated as part of decryption.
If your symmetric encryption algorithm does not use an authentication tag, then this should be an empty octet sequence.
J7WDOFLRRPJ7lLpTfN2mOiXLDg5xtaF-sLQ4mOeN5oc
To learn more about JWE, check out RFC7516.
JWE encryption algorithms
JWE doesn’t have a great range of encryption algorithms available to it, and you’ll find an even more restricted choice in most identity vendors. That’s if they support JWE at all.
For asymmetric encryption, by far, the most common option is RSA. This is RSAES-OAEP (alg: RSA-OAEP) rather than the broken RSAES-PKCS1-v1_5 (alg: RSA1_5). ECDH-ES is available, but I’ve yet to find an identity vendor or product that supports this out of the box. When using JWE, it’s a good idea to see if ECDH-ES is available first before falling back to RSA.
For symmetric encryption, most systems seem to support AES-CBC with HMAC for authenticated encryption; however, JWE also supports the recommended alternative of AES-GCM. Better symmetric encryption algorithms, such as XChaCha20-Poly1305, are not officially supported for JWE, but some community implementations are available.
No matter the asymmetric and symmetric encryption algorithms you choose, don’t forget to make sure that they both provide the same level of security and do not undermine one another.
You can find the full list of encryption and key wrapping algorithms supported by JOSE in the JOSE IANA registry.
To learn more about JSON Web Tokens, JWE, and JWS, check out RFC 7516 or my JWT Fundamentals course on Pluralsight.