JavaScript Object Signing and Encryption (JOSE)

JOSE is a specification for signing and encrypting request and response bodies in web requests. Fundamentally, JOSE is not too complicated, but it can be overwhelming to get started with. This blog post is a guide to help you understand the concepts involved when implementing a JOSE flow into your services. Read postChevron pointing down
Skills and career >
Education and interests >

JavaScript Object Signing and Encryption (JOSE)

JavaScript Object Signing and Encryption (JOSE) is a specification for signing, encrypting, decrypting and verifying payloads. The term payload refers to a request body or a response body. This means that for a GET request, the outbound request is not signed or encrypted, but the response may be if it contains a response body.

In a JOSE system, when we send and receive requests they are signed and encrypted using JOSE methods. This is an asymmetrical form of signing and encrypting payloads, meaning that the Sender and Receiver each have their own public/private key pairs, and the private keys do not need to be shared outside the organisation. This also means that in the event of a leak, the other party does not need to update their keys.

The payload is signed to prove that the sender is who they claim to be. The payload is encrypted so that only the intended receiver can read the contents of the payload.

Overview of sending a payload

  1. Sign the payload using Sender’s private key to produce a signed token
  2. Encrypt signed payload token using a randomly-generated Content Encryption Key (CEK)
  3. Encrypt CEK using Receiver’s public key
  4. Create token containing encrypted signed token and encrypted CEK
  5. Send token

Overview of decrypting and verifying a payload

  1. Decrypt CEK using Receiver’s private key (matching pair of the public key used for encryption)
  2. Use decrypted CEK to decrypt received token and get signed payload token
  3. Use Sender’s public key to verify signature on signed payload token

Requirements for using JOSE

There are libraries available for creating keys, signing, verifying, encrypting and decrypting. You should use a JOSE library instead of trying to write your own.

Each party need to produce at least two public/private key pairs. One key pair will be used for signing, and one will be used for encryption. A signing key pair is generated using a signing algorithm, and an encryption key pair is generated using an encryption algorithm. Make sure you choose an asymmetric key type (RSA, EC or ECDH/OKP). Each party needs to support the same algorithms. Public keys must be stored in JSON Web Key form.

RFC 7518 contains the full list of accepted algorithms for signing and encryption.

JSON Web Key (JWK)

A JSON Web Key (JWK) is a JSON form for storing keys. A key in JWK form contains attributes describing the key, the algorithm(s) used to generate it, and the identifier for the key.

You should host your keys in a JWK Keyset - this is a JSON format with a single attribute keys which holds an array of JWKs. An example JWK Keyset can be found at jwkset.com.

JWKs used for signing and encryption differ - signing JWKs have a single algorithm defined, while encryption JWKs have two. The details of these algorithms can be found in the Signing and Encryption sections below.

Preparing a payload to be sent

Signing

The signing process takes three elements and produces a single, base 64 encoded string. The three elements are the Signature Header, the Payload, and the Signing JWK. The string produced at the end is known as a JSON Web Signature (JWS) Token but is most commonly used as a JSON Web Token (JWT). This token is produced by a process called 'compact serialisation'.

Signature Header

The Signature Header is a JSON Object containing data about the JWS. It has two required keys:

It may also have an exp field, but this is not officially supported in the JOSE specification. As this is a custom field you should use the crit header to list your custom fields.

{
  "alg": "RS384",
  "kid": "service_2025_sig_rsa_RS384",
  "exp": 165297738,
  "crit": ["exp"]
}
Payload

The request body you want to send - can contain any data in JSON form.

  {
    "email": "user@example.com",
    "password": "6N6E<lk/`!0(ySsm"
  }
Signing JWK

The JWK contains the data for the signing algorithm and key to use to produce a signature.

{
  "kty": "RSA",
  "use": "sig",
  "alg": "RS384",
  "e": "AQAB",
  "kid": "service_2025_sig_rsa_RS384",
  "n": "xORciQJITiY1Dy7HI_vZQl1Td3zV8lKZ4RS1HarZ4zW4CzdVtQ7lpWdlnC"
}
JWS output

The output of the signing process will be a base 64 encoded string/token. The token contains the Header, the Payload, and a signature.

"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ"

Encrypting

The encryption process takes four elements and produces a single, base 64 encoded string. The four elements are the Encryption Header, the Signed Payload, the Content Encryption Key (CEK) and the Encrypting JWK. The string produced at the end is known as a JSON Web Encryption (JWE) Token. This token is produced by a process called 'compact serialisation'.

Encryption Header

The Encryption Header is a JSON Object containing data about the JWE. It has three required keys:

It may also have an optional zip field which specifies the compression algorithm to use if the Signed Payload needs to be compressed before encryption.

{
  "alg": "RSA-OAEP-256",
  "enc": "A256GCM",
  "kid": "service_2025_enc_rsa_RSA-OAEP-256",
  "zip": "DEF"
}
Signed Payload

The Signed Payload is the JWS token generated with the process described above, passed in as a string. It is encrypted using the randomly-generated Content Encryption Key and the algorithm specified in the enc header.

"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ"

Technically, encryption in the JOSE standard doesn't need the payload to be signed, and could just use any string, but to get the full benefits this should be the JWS token.

Content Encryption Key (CEK)

The CEK is a randomly-generated string, used with the algorithm specified in the enc header to encrypt the Signed Payload. The CEK itself is then encrypted using the algorithm specified in the alg header.

"\x99\x16\x02\xCB\x81f\xC0\x90s\xABB\xC3\x93\xB2\xAB\xA0\xFCx\\\x14\xB5\xBB\x88\xF8R\r\x96\xAFW\x17\xE4\xAF"
Encrypting JWK

The encrypting JWK is a JWK representing the public key from the Receiver's public JWK set, used during encryption of both the Signed Payload and the CEK. The JWK's attributes vary depending on the algorithm used but all types must include a kid attribute to identify the JWK.

  {
      "kty": "RSA",
      "use": "enc",
      "alg": "RSA-OAEP-256",
      "e": "AQAB",
      "enc": "A128GCM",
      "kid": "service_2025_enc_rsa_RSA-OAEP-256",
      "n": "3K1ODfxz_bHAqFuLlKv4NMicKku7KB93u_nkMu5DxS2u8U3wK-L3"
  }

This JWK is an example for an RSA encryption keyset. Other encryption keyset types will need different attributes specified, but your JOSE library should be able to generate JWKs for you from the public/private keyset you generate.

JWE output

The output of the encryption process will be a base 64 encoded string/token. The token contains the Header, the encrypted CEK, the Encrypted and Signed Payload, an automatically-generated Initialisation Vector if required by the chosen algorithm, and an automatically-generated Authentication Tag used to verify the integrity of the Encrypted and Signed Payload.

eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe
ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb
Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV
mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8
1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi
6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji
SdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ

Decrypting and verifying a received JOSE payload

The decryption and verification process is similar to the signing and encryption process, but in reverse. The Receiver's matching private key is used to decrypt the CEK and Signed Payload, and the Sender's matching public key is used to verify the signature of the decrypted payload.

Notes and further reading

The full specification for JOSE can be found across various RFC documentation, from RFC 7515 to RFC 7520:

Home page >