JWS and JWT policies overview

This topic provides general information about JWT (JSON Web Token) and JWS (JSON Web Signature) and the Apigee JWS/JWT policies that may be of interest to Apigee proxy developers.

Introduction

Both JWS and JWT are commonly used to share claims or assertions between connected applications. The JWS/JWT policies enables Edge API proxies to:

  • Generate a signed JWT or JWS.
  • Verify a signed JWT or JWS and claims within the JWS/JWT.
  • Decode a signed JWT or JWS without validating the signature.

In the latter two cases, the policy also sets variables that allow additional policies, or the backend services themselves, to inspect the validated claims and to make decisions based on those claims.

When using the Verify JWS/JWT policy, an invalid JWS/JWT will be rejected and will result in an error condition. Similarly, when using the Decode JWS/JWT policy, a malformed JWS/JWT will result in an error condition.

Videos

Watch a short video for a quick introduction to JWT. While this video is specific to generating a JWT, many of the concepts are the same for JWS.

What a short video to learn more about the JWT structure.

Use cases

You can use the JWS/JWT policies to:

  • Generate a new JWS/JWT on either the proxy or target endpoint sides of an Edge proxy. For example, you might create a proxy request flow that generates a JWS/JWT and returns it to a client. Or, you might design a proxy so that it generates a JWS/JWT on the target request flow, and attaches it to the request sent to the target. Those claims would then be available to enable backend services to apply further security processing.
  • Verify and extract claims from a JWS/JWT obtained from inbound client requests, from target service responses, from Service Callout policy responses, or from other sources. Edge will verify the signature on a JWS/JWT, whether the JWS/JWT was generated by a third-party, or by Edge itself, using either RSA or HMAC algorithms.
  • Decode a JWS/JWT. Decoding is most useful when used in concert with the Verify JWS/JWT policy, when the value of a claim (JWT) or header (JWS/JWT) from within the JWS/JWT must be known before verifying the JWS/JWT.

Parts of a JWS/JWT

A signed JWS/JWT encodes information in three parts separated by periods: the header, the payload, and the signature:

header.payload.signature
  • The Generate JWS/JWT policy creates all three parts.
  • The Verify JWS/JWT policy examines all three parts.
  • The Decode JWS/JWT policy examines the header and payload only.

A JWS also supports a detached format that omits the payload from the JWS:

header..signature

With a detached JWS, the payload is sent separately from the JWS. You use the <DetachedContent> element of the Verify JWS policy to specify the raw, unencoded JWS payload. The Verify JWS policy then verifies the JWS by using the header and signature in the JWS and the payload specified by the <DetachedContent> element.

To learn more about tokens and how they are encoded and signed, see:

Differences between JWS and JWT

You can use either a JWT or JWS to share claims or assertions between connected applications. The major difference between the two is the representation of the payload:

  • JWT
    • The payload is always a JSON object
    • The payload is always attached to the JWT
    • The typ header of the token is always set to JWT
  • JWS
    • The payload can be represented by any format, such as a JSON object, byte stream, octet stream, and others
    • The payload does not have to be attached to the JWS

Because the JWT format always uses a JSON object to represent the payload, the Edge Generate JWT and Verify JWT policies have built in support to handle common Registered Claim Names, such as aud, iss, sub, and others. That means you can use elements of the Generate JWT policy to set these claims in the payload, and elements of the Verify JWT policy to verify their values. See the Registered Claim Names section of the JWT specification for more.

Along with supporting certain Registered Claim Names, the Generate JWT policy directly supports adding claims with arbitrary names to the JWT. Each claim is a simple name/value pair, where the value can be of type number, boolean, string, map, or array.

Because a JWS can use any data representation for the payload, you cannot add claims to the payload. The Generate JWS policy does support adding claims with arbitrary names to the header of the JWS. Also, the JWS policies support a detached payload, where the JWS omits the payload. A detached payload lets you send the JWS and payload separately and is required by several security standards.

About signature algorithms

The JWS/JWT Verification and JWS/JWT Generation policies support RSA, RSASSA-PSS, ECDSA, and HMAC algorithms, using SHA2 checksums of bit strength 256, 384, or 512. The JWS/JWT Decode policy works regardless of the algorithm that was used to sign the JWS/JWT.

HMAC algorithm

The HMAC algorithm relies on a shared secret, known as the secret key, for creating the signature (also known as signing the JWS/JWT) and for verifying the signature.

The minimum length of the secret key depends on the bit strength of the algorithm:

  • HS256: 32 bytes minimum key length
  • HS386: 48 bytes minimum key length
  • HS512: 64 bytes minimum key length

RSA algorithm

The RSA algorithm uses a public/private key pair for the cryptographic signature. With RSA signatures, the signing party uses an RSA private key to sign the JWS/JWT, and the verifying party uses the matching RSA public key to verify the signature on the JWS/JWT. There are no size requirements on the keys.

RSASSA-PSS algorithm

The RSASSA-PSS algorithm is an update to the RSA algorithm. Like RSS, RSASSA-PSS uses an RSA public/private key pair for the cryptographic signature. The format of the key is the same as for RSS. The signing party uses a private key to sign the JWS/JWT, and the verifying party uses the matching public key to verify the signature on the JWS/JWT. There are no size requirements on the keys.

ECDSA algorithm

The Elliptic Curve Digital Signature Algorithm (ECDSA) algorithm is an elliptic-curve cryptography algorithm with a P-256, P-384, and P-521 curve. When you use ECDSA algorithms, the algorithm determines the type of public and private key you must specify:

Algorithm Curve Key requirement
ES256 P-256 A key generated from the P-256 curve (also know as secp256r1 or prime256v1)
ES384 P-384 A key generated from the P-384 curve (also know as secp384r1)
ES512 P-521 A key generated from P-521 curve (also known as secp521r1)

Key encryption algorithms

The JWS/JWT policies support all key encryption algorithms supported by the OpenSSL.

Using a JSON Web Key Set (JWKS) to verify a JWS/JWT

When you verify a signed JWS/JWT, you need to provide the public key that is associated with the private key used to sign the token. You have two options for providing the public key to the verify JWS/JWT policies:

  • use the actual public key value (typically provided in a flow variable), or
  • use a public key wrapped in a JWKS.

About JWKS

A JWKS is a JSON structure that represents a set of JSON Web Keys (JWKs). A JWK is a JSON data structure that represents a cryptographic key. JWK and JWKS are described in RFC7517. See JKWS examples at Appendix A. Example JSON Web Key Sets

JWKS structure

RFC7517 describes the JKWS key elements for each key type, such as "RSA" or "EC". For example, depending on the key type, these parameters can include:

  • kty - The key type, such as "RSA" or "EC".
  • kid (the key id) - Can be any arbitrary value (no duplicates within a key set). If the inbound JWT bears a key ID which is present in the set of JWKS, then the policy will use the correct public key to verify the JWS/JWT signature.

Following are examples of optional elements and their values:

  • alg - The key algorithm. It must match the signing algorithm in the JWS/JWT.
  • use - If present, must be sig.

The following JWKS includes the required elements and values and would be valid on Edge (from https://www.googleapis.com/oauth2/v3/certs):

{  
   "keys":[  
      {  
         "kty":"RSA",
         "alg":"RS256",
         "use":"sig",
         "kid":"ca04df587b5a7cead80abee9ea8dcf7586a78e01",
         "n":"iXn-WmrwLLBa-QDiToBozpu4Y4ThKdwORWFXQa9I75pKOvPUjUjE2Bk05TUSt7-V7KDjCq0_Nkd-X9rMRV5LKgCa0_F8YgI30QS3bUm9orFryrdOc65PUIVFVxIwMZuGDY1hj6HEJVWIr0CZdcgNIll06BasclckkUK4O-Eh7MaQrqb646ghFlG3zlgk9b2duHbDOq3s39ICPinRQWC6NqTYfqg7E8GN_NLY9srUCc_MswuUfMJ2cKT6edrhLuIwIj_74YGkpOwilr2VswKsvJ7dcoiJxheKYvKDKtZFkbKrWETTJSGX2Xeh0DFB0lqbKLVvqkM2lFU2Qx1OgtTnrw",
         "e":"AQAB"
      },
      {
          "kty":"EC",
          "alg":"ES256",
          "use":"enc",
          "kid":"k05TUSt7-V7KDjCq0_N"
          "crv":"P-256",
          "x":"Xej56MungXuFZwmk_xccvsMpCtXmqhvEEMCmHyAmKF0",
          "y":"Bozpu4Y4ThKdwORWFXQa9I75pKOvPUjUjE2Bk05TUSt",
      }
   ]
}

Designing your proxy to use JWKS

When a JWS/JWT is obtained from an issuer, often the issuer inserts a Key ID (or kid) into the JWS/JWT header. The key tells the recipient of the JWS/JWT how to find the public or secret key necessary to verify the signature on the signed JWS/JWT.

As an example, suppose an issuer signs a JWT with a private key. The "Key ID" identifies the matching public key to use to verify the JWT. The list of public keys is typically available at some well-known endpoint, for example: https://www.googleapis.com/oauth2/v3/certs.

This is the basic sequence that Edge (or any platform that works with JWKS) needs to perform to work with a JWS/JWT that has a JWKS:

  1. Examine the JWS/JWT header to find the Key ID (kid).
  2. Examine the JWS/JWT header to find the signing algorithm (alg), such as RS256.
  3. Retrieve the list of keys and IDs from the JWKS of the well-known endpoint for a given issuer.
  4. Extract the public key from the list of keys with the key ID noted in the JWS/JWT header and with the matching algorithm, if the JWKS key specifes the algorithm.
  5. Use that public key to verify the signature on the JWS/JWT.

As an Edge API proxy developer you need to do the following to perform JWS/JWT verification:

  1. Retrieve the list of keys and IDs from the well-known endpoint for a given issuer. You can use a Service Callout policy for this step.
  2. In the Verify JWS/JWT policy, specify the location of the JWS/JWT in the <Source> element and the JWKS payload in the <PublicKey/JWKS> element. For example, for the VerifyJWT policy:
    <VerifyJWT name="JWT-Verify-RS256">
        <Algorithm>RS256</Algorithm>
        <Source>json.jwt</Source>
        <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
        <PublicKey>
            <JWKS ref="public.jwks"/>
        </PublicKey>
        <Subject>apigee-seattle-hatrack-montage</Subject>
        <Issuer>urn://apigee-edge-JWT-policy-test</Issuer>
        <Audience>urn://c60511c0-12a2-473c-80fd-42528eb65a6a</Audience>
        <AdditionalClaims>
            <Claim name="show">And now for something completely different.</Claim>    
        </AdditionalClaims>
    </VerifyJWT>
    

The Verify JWT policy does everything else:

  • If a key with a Key ID that matches the Key ID (kid) asserted in the JWT is not found in the JWKS, then the Verify JWT policy throws an error and does not validate the JWT.
  • If the inbound JWT does not bear a key ID (kid) in the header, then this mapping of keyid-to-verification-key is not possible.

As the proxy designer, you are responsible for determining the key to use; in some cases this may be a fixed, hard-coded key.