Validating the JSON Web Token

When the system has returned the transient JWT, validate the token's authenticity. Retrieve the public key signature that is part of the transient JWT and compare that signature with the public key returned from
Cybersource
.
Follow these steps to validate the key:
  1. Retrieve the public key ID (
    kid
    ) from the transient JWT header.
  2. Retrieve the public key from
    Cybersource
    .
  3. Validate the public key signature.

Retrieving the Public Key ID

A JSON Web Token (JWT) includes these three elements:
  • Header
  • Payload
  • Signature
Each element is separated by a period (.) in this format:
header.payload.signature
.
The
kid
parameter within the JWT header is the public key ID. You use this ID to request the public key using the
/flex/v2/public-keys/
{kid}
endpoint.

Decrypting the JWT Header

The JWT is Base64-encoded. You must decrypt the token before you can see the
kid
parameter.
Example: Header
    
eyJraWQiOiJ6dSIsImFsZyI6IlJTMjU2In0K
Example: Decrypting Header on the Command Line
    
ejava.io.PrintWriter@2edd3f5c cho 'eyJraWQiOiJ6dSIsImFsZyI6IlJTMjU2In0K' | base64 --decode
Example: Output
    
{"kid":"zu","alg":"RS256"}

Retrieving the Public Key

When you obtain the
kid
value from the JWT header, use that value to retrieve the public key. To retrieve the public key, send a request to the
/flex/v2/public-keys/
{kid}
endpoint.
The public key is returned as a JSON Web Key (JWK).
Request
Endpoint:
GET https://
apitest.cybersource.com
/flex/v2/public-keys/zu
  
{}
Response to Successful Request
  
{ "kty": "RSA", "use": "enc", "kid": "zu", "n": "ozmvkuGzWNHs9cEcC5PWwbG-dmSjPcoQFxEbqH_fBjkj_nfTTKshdiSq5ciulWEa_rrqQ2qwcSADNxtTzRR1qfud-NvsM8Vlt T7xDuVVqPTZoWLKa0BWXgQQ-1mCm1KdGltYWccB0R1LoF-rb3DEEZySsHvqErYzYt4M_rqjEiK5Y9y1h3k1h5Yk4zGLWchko3 jiDS-pVevvWsQsN-Y3KuB19485G9P_MXLtfJWQ4wC4jlo9etdD_hgDfxX-hQy3wuwHfHifLdxvxiB8X5Is4m6DuY4_7hS5RwX AjO1QSd-zUYZNT_2yWVR56_jyiZEiOdgIm9QtLPZCjava.io.PrintWriter@4ebd1ff4 TKzqsXoqZQ", "e": "AQAB" }

JAVA Example: Validating the Transient Token

The Java code below can be used to validate the transient token with the public key.
    
package com.
cybersource
.example.service; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.
cybersource
.example.config.ApplicationProperties; import com.
cybersource
.example.domain.CaptureContextResponseBody; import com.
cybersource
.example.domain.CaptureContextResponseHeader; import com.
cybersource
.example.domain.JWK; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.math.BigInteger; import java.security.KeyFactory; import java.security.interfaces.RSAPublicKey; import java.security.spec.RSAPublicKeySpec; import java.util.Base64; import java.util.Base64.Decoder; @Service @RequiredArgsConstructor public class JwtProcessorService { @Autowired private final ApplicationProperties applicationProperties; @SneakyThrows public String verifyJwtAndGetDecodedBody(final String jwt) { // Parse the JWT response into header, payload, and signature final String[] jwtChunks = jwt.split("\\."); final Decoder decoder = Base64.getUrlDecoder(); final String header = new String(decoder.decode(jwtChunks[0])); final String body = new String(decoder.decode(jwtChunks[1])); // Normally you'd want to cache the header and JWK, and only hit /flex/v2/public-keys/{kid} when the key rotates. // For simplicity and demonstration's sake let's retrieve it every time final JWK publicKeyJWK = getPublicKeyFromHeader(header); // Construct an RSA Key out of the response we got from the /public-keys endpoint final BigInteger modulus = new BigInteger(1, decoder.decode(publicKeyJWK.n())); final BigInteger exponent = new BigInteger(1, decoder.decode(publicKeyJWK.e())); final RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent)); // Verify the JWT's signature using the public key final Algorithm algorithm = Algorithm.RSA256(rsaPublicKey, null); final JWTVerifier verifier = JWT.require(algorithm).build(); // This will throw a runtime exception if there's a signature mismatch. verifier.verify(jwt); return body; } @SneakyThrows public String getClientVersionFromDecodedBody(final String jwtBody) { // Map the JWT Body to a POJO final CaptureContextResponseBody mappedBody = new ObjectMapper().readValue(jwtBody, CaptureContextResponseBody.class); // Dynamically retrieve the client library return mappedBody.ctx().stream().findFirst() .map(wrapper -> wrapper.data().clientLibrary()) .orElseThrow(); } @SneakyThrows private JWK getPublicKeyFromHeader(final String jwtHeader) { // Again, this process should be cached so you don't need to hit /public-keys // You'd want to look for a difference in the header's value (e.g. new key id [kid]) to refresh your cache final CaptureContextResponseHeader mappedJwtHeader = new ObjectMapper().readValue(jwtHeader, CaptureContextResponseHeader.class); final RestTemplate restTemplate = new RestTemplate(); final ResponseEntity response = restTemplate.getForEntity( "https://" + applicationProperties.getRequestHost() + "/flex/v2/public-keys/" + mappedJwtHeader.kid(), String.class); return new ObjectMapper().readValue(response.getBody(), JWK.class); } }