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:
- Retrieve the public key ID (kid) from the transient JWT header.
- Retrieve the public key fromCybersource.
- Validate the public key signature.
Retrieving the Public Key ID
A JSON Web Token (JWT) includes these three elements:
- Header
- Payload
- Signature
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/
endpoint.{kid}
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
echo '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/
endpoint.{kid}
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_jyiZEiOdgIm9QtLPZCTKzqsXoqZQ", "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<String> response = restTemplate.getForEntity( "https://" + applicationProperties.getRequestHost() + "/flex/v2/public-keys/" + mappedJwtHeader.kid(), String.class); return new ObjectMapper().readValue(response.getBody(), JWK.class); } }