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.
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.
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.
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'.
The Signature Header is a JSON Object containing data about the JWS. It has two required keys:
alg - the signing algorithm used. This needs to be supported by both parties (i.e. both parties
need to have a public/private key pair for that algorithm).kid - the key ID. Used to identify which key pair was used during the signing process.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"]
}
The request body you want to send - can contain any data in JSON form.
{
"email": "user@example.com",
"password": "6N6E<lk/`!0(ySsm"
}
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"
}
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"
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'.
The Encryption Header is a JSON Object containing data about the JWE. It has three required keys:
enc - the encryption algorithm used to encrypt the Signed Payload. Both parties must have a
public/private key pair which supports this and the alg used.alg - the encryption algorithm used to encrypt the CEK. Both parties must have a public/private
key
pair which supports this and the enc used.kid - the key ID. Used to identify which key pair was used during the signing process.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"
}
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.
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"
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.
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
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.
The full specification for JOSE can be found across various RFC documentation, from RFC 7515 to RFC 7520: