In this article
March 13, 2025
March 13, 2025

The ABCs of token security: JWS, JWE, JWK, and JWKS explained

Confused by all the token jargon? This article simplifies JWS, JWE, JWK, and JWKS, showing you how each one ensures your data stays secure and trustworthy.

JWTs are like little security passports that let you pass data between parties in a way that’s compact, safe, and tamper-proof. But here’s the catch—there’s more to JWTs than just slapping a token together. You also need a set of technologies to sign them (so they’re legit), encrypt them (so no one can peek inside), and verify them (so they can’t be forged). That’s where JWS, JWE, JWK, and JWKS come into play.

In this article, we’re going to break down these fancy acronyms and show you how they work together to keep your web apps secure. We’ll dive into what each of these technologies does, when you’d use them, and why they matter. By the end, you’ll not only understand how JWTs fit into the bigger picture but also how to use these tools to level up your app’s security game.

The TL;DR

  • JWS ensures the integrity of JWTs through signing.

  • JWE ensures the confidentiality of JWTs through encryption.

  • JWK and JWKS help manage and distribute public keys.

  • JWA defines the cryptographic algorithms used for all of the above.

Keep reading for details on each one.

JWS: Signing your tokens

JWS stands for JSON Web Signature. It's a way to securely sign a JWT so that the recipient can verify that it hasn't been tampered with and came from a trusted source.

JWS allows you to attach a digital signature to a piece of data, which proves the sender's identity. Anyone receiving the signed data can check the signature to confirm the data hasn't been changed and that it comes from the claimed sender.

Here’s how JWS works:

  1. You start with a piece of data that you want to sign. This is usually a JWT that contains claims (i.e., information about the subject) and other relevant details.
  2. Before signing the data, you encode it in a specific way. The data (such as the JSON) is first serialized (converted to a string format) and then Base64URL encoded. Base64URL is a slightly modified version of Base64 encoding that is safe to use in URLs. This encoded data is now the "payload" of the JWS.
  3. Next, you generate a signature over the encoded data using a cryptographic algorithm and a secret key (for HMAC) or a private key (for asymmetric algorithms like RSA or ECDSA).
    1. HMAC (Hash-based Message Authentication Code): If you're using a shared secret key, HMAC is used to hash the payload along with the secret key.
    2. RSA or ECDSA: If you're using asymmetric keys, the private key is used to sign the payload, creating the signature. In this case the recipient will verify the signature using the corresponding public key.
  4. The final JWS is made up of three parts:
    1. Header: Contains metadata about the signature, such as the algorithm used (e.g., "HS256" for HMAC SHA-256 or "RS256" for RSA SHA-256).
    2. Payload: The data that was signed (Base64URL encoded).
    3. Signature: The cryptographic signature that ensures the integrity and authenticity of the payload (Base64URL encoded).
  5. Once the JWS is created, it is sent over to the recipient. The recipient will use the signature to verify the integrity and authenticity of the data.
  6. The recipient first decodes the header to figure out which cryptographic algorithm was used and, if necessary, the public key to verify the signature.
  7. The recipient takes the encoded payload, applies the cryptographic algorithm using the appropriate key (public key for asymmetric algorithms or shared secret for HMAC), and compares the generated signature with the one sent in the JWS.
  8. If the generated signature matches the signature in the JWS, the data is verified and trusted. If they don't match, the data has been tampered with or isn't from a trusted source.

JWE: Encrypting your tokens

JWE (JSON Web Encryption) provides a way to encrypt data in JSON format securely. It's used when the data needs to be both confidential and verifiable by different parties (but not necessarily tamper-proof like JWS).

Technically, JWE works by encrypting the payload (the data) with a content encryption key (CEK), which is itself encrypted using a key encryption key (KEK). This provides multiple layers of security to ensure that the data remains confidential while it is in transit.

A JWE consists of five components, each separated by a period (.):

  • Header: Defines the algorithms for encrypting the content encryption key (CEK) and the payload.

  • Encrypted key: The encrypted content encryption key (CEK), which is used to encrypt the payload.

  • Initialization vector (IV): A random value used in certain encryption algorithms (like AES) to ensure that encrypting the same data multiple times results in different ciphertexts.

  • Ciphertext: The actual encrypted payload (the data).

  • Authentication tag: A cryptographic tag used for ensuring integrity, helping to verify that the data hasn't been altered during transmission.

The JWE is structured like this:

<Header>.<Encrypted Key>.<IV>.<Ciphertext>.<Authentication Tag>

A JWE header looks like this:

{
  "alg": "RSA-OAEP",           // Key encryption algorithm (RSA)
  "enc": "A256GCM"             // Content encryption algorithm (AES GCM with 256-bit key)
}

Let’s break down how JWE encryption works:

  1. First, a content encryption key (CEK) is generated using a secure random number generator.
  2. The CEK is encrypted using the key specified in the alg field of the header (often done with public key encryption like RSA).
  3. The payload (the actual data) is encrypted using the CEK and the chosen content encryption algorithm specified in the "enc" field (e.g., AES). If using AES GCM, this encryption also produces an authentication tag, which is used for integrity checking.
  4. A random IV is generated and used in the encryption process, and an authentication tag is created to ensure integrity.
  5. All the parts (header, encrypted key, IV, ciphertext, authentication tag) are combined and encoded in base64 to create the final JWE token.

After the encrypted message is sent, it’s decrypted by the recipient using this flow:

  1. Extract the different parts of the JWE (header, encrypted key, IV, ciphertext, authentication tag).
  2. Use their private key (in the case of public/private key encryption) to decrypt the encrypted CEK.
  3. Use the decrypted CEK and the IV, to decrypt the ciphertext to recover the original data.
  4. Verify the authentication tag to ensure the data hasn't been tampered with during transmission.

JWK and JWKS: Representing and sharing cryptographic keys

A JWK (JSON Web Key) is a way to represent a cryptographic key in JSON format. It’s used in web apps to represent public keys in systems like RSA or EC, helping keep data secure. The JWK format is used for tasks like signing, checking, encrypting, and decrypting data.

A JWKS (JSON Web Key Set) is an array of multiple JWKs. This allows for key rotation or support for different algorithms.

A typical JWK consists of the following fields:

  • kty (key type): The algorithm or key type (e.g., RSA, EC, or octet sequence).

  • use (key use): Specifies how the key is intended to be used, e.g., for signing (sig) or encryption (enc).

  • kid (key ID): An optional key identifier that can be used to reference the key.

  • alg (algorithm): The algorithm for which the key is intended, e.g., RS256 for RSA with SHA-256.

  • RSA specific fields:

    • n (modulus): The modulus part of the RSA public key (base64url encoded).
    • e (exponent): The exponent part of the RSA public key (base64url encoded).
    • d (private exponent), p (prime factor), q (prime factor), dp, dq, and qi for RSA private keys (usually not shared publicly).

  • Elliptic curve (EC) specific fields:

    • x and y: The X and Y coordinates.

  • Octet (symmetric) keys:

    • k: The key itself.

An RSA public key represented as a JWK looks like this:

{
  "kty": "RSA",
  "n": "sXch5RH6dsZk98yFdbBexcmGxQPH2bQ7B6J7huINz6X9iOWbtNflRkwz35gXBkXcZIqeQxhZpN0b5E1J0EIsqHh_tciN4ivMvsE9JxjueQL7xZ7G2NS6YfDlmmuS2N7HXhA0kFf0ZZ7f7QbAy2gDkx1_UpcB2OYXt2WhYZ4gxY74mcXg1G8UtnscwQZcbY5H7QvmPY0oqHl3tbYHDoAj3gZsvO1wr26Wcv4AYzzb2lgRSc9rxuK68YgxPpfxn6k7ovLRIxiAoGUj2V43dH0LZ6Lvft41rtQ0RzXckSQoXYv3G7PR8_zX-lTkTAk61N_6b8L9rd43VOrlx0g",
  "e": "AQAB",
  "kid": "my-key-id",
  "use": "sig",
  "alg": "RS256"
}

A JWKS looks like this:

{
  "keys": [
    {
      "kty": "RSA",
      "n": "sXch5RH6dsZk98yFdbBexcmGxQPH2bQ7B6J7huINz6X9iOWbtNflRkwz35gXBkXcZIqeQxhZpN0b5E1J0EIsqHh_tciN4ivMvsE9JxjueQL7xZ7G2NS6YfDlmmuS2N7HXhA0kFf0ZZ7f7QbAy2gDkx1_UpcB2OYXt2WhYZ4gxY74mcXg1G8UtnscwQZcbY5H7QvmPY0oqHl3tbYHDoAj3gZsvO1wr26Wcv4AYzzb2lgRSc9rxuK68YgxPpfxn6k7ovLRIxiAoGUj2V43dH0LZ6Lvft41rtQ0RzXckSQoXYv3G7PR8_zX-lTkTAk61N_6b8L9rd43VOrlx0g",
      "e": "AQAB",
      "kid": "my-key-id",
      "use": "sig",
      "alg": "RS256"
    },
    {
      "kty": "EC",
      "crv": "P-256",
      "x": "f83OJ3D2xF4SywFrhC9qvry4i57zqfEDEyAt91l7Gh8",
      "y": "x_FEzRuCVtgsOVkxQZ2Bym5EowJzZo0gYqvJZcCEKQvE",
      "kid": "my-ec-key-id",
      "use": "sig",
      "alg": "ES256"
    }
  ]
}

When an application receives a JWT (JSON Web Token) and needs to verify its signature, it can use the public key stored as a JWK. The JWK can either be provided directly or retrieved from a JWK Set endpoint (often found in OAuth2 or OpenID Connect configurations where a public key may be needed to verify JWT signatures).

A typical JWKS endpoint looks like this:

https://example.com/.well-known/jwks.json

A response from this endpoint would contain a JSON array of JWKs.

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "my-key-id",
      "use": "sig",
      "alg": "RS256",
      "n": "sXch5RH6dsZk98yFdbBexcmGxQPH2bQ7B6J7huINz6X9iOWbtNflRkwz35gXBkXcZIqeQxhZpN0b5E1J0EIsqHh_tciN4ivMvsE9JxjueQL7xZ7G2NS6YfDlmmuS2N7HXhA0kFf0ZZ7f7QbAy2gDkx1_UpcB2OYXt2WhYZ4gxY74mcXg1G8UtnscwQZcbY5H7QvmPY0oqHl3tbYHDoAj3gZsvO1wr26Wcv4AYzzb2lgRSc9rxuK68YgxPpfxn6k7ovLRIxiAoGUj2V43dH0LZ6Lvft41rtQ0RzXckSQoXYv3G7PR8_zX-lTkTAk61N_6b8L9rd43VOrlx0g"
    }
  ]
}

JWKs make the best practice of rotating encryption keys effortless for developers since keys can be changed without affecting the system. As the new key is added to the JWKS endpoint, clients can start using it seamlessly.

Bonus term: JWA

JWA (JSON Web Algorithms) is a specification that defines the cryptographic algorithms used to secure JSON Web Tokens (JWTs), JSON Web Encryption (JWE), and JSON Web Signature (JWS). It helps ensure that different systems can securely work together using the same algorithms.

JWA defines a set of algorithms used for creating signatures that can be verified using a public key (or secret key) and encrypting data in a way that only authorized parties can decrypt it. To be more specific:

  • JWA is used to define the signing algorithms used in JWS:

    • HMAC-based (Hash-based Message Authentication Code):
      • HS256: HMAC with SHA-256.
      • HS384: HMAC with SHA-384.
      • HS512: HMAC with SHA-512.
    • RSA (asymmetric encryption):
      • RS256: RSA with SHA-256.
      • RS384: RSA with SHA-384.
      • RS512: RSA with SHA-512.
    • Elliptic Curve (EC) (asymmetric encryption):
      • ES256: ECDSA with P-256 curve and SHA-256.
      • ES384: ECDSA with P-384 curve and SHA-384.
      • ES512: ECDSA with P-521 curve and SHA-512.
    • RSA-PSS (asymmetric encryption): PS256,
      • PS256: RSA PSS with SHA-256.
      • PS384: RSA PSS with SHA-384.
      • PS512: RSA PSS with SHA-512.
    • EdDSA (Edwards-curve Digital Signature Algorithm):
      • EdDSA: A signing algorithm based on elliptic curves, specifically designed to be faster and more secure than traditional ECDSA.

  • JWA defines the encryption algorithms used in JWE:

    • Symmetric Key Algorithms (used for encryption/decryption of the data):
      • A128CBC-HS256: AES with 128-bit key in CBC mode and HMAC with SHA-256.
      • A192CBC-HS384: AES with 192-bit key in CBC mode and HMAC with SHA-384.
      • A256CBC-HS512: AES with 256-bit key in CBC mode and HMAC with SHA-512.
      • A128GCM: AES with 128-bit key in GCM mode.
      • A192GCM: AES with 192-bit key in GCM mode.
      • A256GCM: AES with 256-bit key in GCM mode.
    • Asymmetric Key Algorithms (used for key wrapping or key management):
      • RSA-OAEP: RSA Optimal Asymmetric Encryption Padding.
      • RSA-OAEP-256: RSA OAEP with SHA-256.
      • ECDH-ES: Elliptic Curve Diffie-Hellman Ephemeral Static for key exchange.
      • ECDH-ES+A128KW: Elliptic Curve Diffie-Hellman for key exchange, with AES-128 for key wrapping.

  • JWA works with JWK to specify which algorithms should be used with specific keys. For example, a JWK may indicate that it should be used with the RS256 algorithm for signing.

    • RSA Key Wrapping:
      • RSA1_5: RSA key wrapping algorithm using PKCS#1 v1.5.
      • RSA-OAEP: RSA key wrapping with Optimal Asymmetric Encryption Padding.
    • Elliptic Curve Key Wrapping:
      • ECDH-ES: Elliptic Curve Diffie-Hellman Ephemeral Static for key exchange.
      • ECDH-ES+A128KW: Uses ECDH-ES for key exchange, with AES key wrapping for encryption.

When to use what

Still confused over what you should use? Use the following guidelines to make your decision.

Sign tokens:

  • To ensure that the token hasn't been altered and that it originated from a trusted party.

  • To ensure that a sender cannot deny having sent a particular token (common in legal, financial, or audit systems).

  • To verify that the contents of the token have not been modified in transit.

Encrypt tokens:

  • To protect sensitive information within the token that should not be exposed to unauthorized parties (e.g., personally identifiable information, private data).

  • When tokens need to be stored securely, encryption ensures that if a storage medium is compromised, the contents of the tokens remain unreadable without the decryption key.

  • To prevent eavesdropping during the transmission of tokens over insecure channels (e.g., HTTP instead of HTTPS).

Use both signing and encryption:

  • When both the integrity and confidentiality of the token are critical. For example, in secure systems where both the data integrity of the token and its contents need to be kept confidential (e.g., tokens containing sensitive user data).

  • For compliance with laws such as GDPR, where sensitive data must be encrypted and its origin must be verified.

Conclusion

And there you have it! Now that we've explored JWS, JWE, JWK, and JWKS, you should have a solid understanding of how these technologies work together to keep your JWTs secure. Whether you're signing tokens to prove their authenticity, encrypting them for confidentiality, or managing keys to ensure everything’s verified properly, these tools are the backbone of secure data exchange in modern web applications.

One last thing to keep in mind is that verifying the signature is only the first step in the process of validating JWTs. Verifying the claims included in the decoded JWT is equally important to keep your users and your app safe:

  • Check the expiration time (exp) and the not-before time (nbf) claims to ensure the JWT is valid.

  • Verify the issuer (iss) claim to ensure the JWT was issued by a trusted party.

  • Verify the audience (aud) claim to ensure the JWT is intended for the correct recipient.

For more on this, see JWT validation: how-to and best libraries to use.

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.