Generate CDC Bearer Token from JWT hashed with RSA Private Key
REST API requests to SAP Customer Data Cloud should be made securely, the recommended authentication mechanism is to use a bearer token constructed using a unique RSA key. An API request to SAP Customer Data Cloud should be signed using an HTTP bearer token. This replaces the application / user key and secret signature method.
What is JWT?
JSON Web Tokens is an open standard URL-safe way for securely transmitting information between a request & response system. Data in a JWT is encoded as a JSON object. This data is digitally signed using a private key pair using RSA.
JSON Web Tokens consist of three parts separated by a dot,
- Header
- Payload
- Signature
Format of JSON Web Token
[header].[body].[signature]Header:
{
"alg": "RS256" – JWT Signing algorithm
"typ": "JWT", - JWT
"kid": "<app key>" - User key from the application
}
Body:
{
"iat": 1548189000, - Timestamp that identifies when the JWT was issued
"jti": "b3t567895-r56y-7659-5t6i-t67y64456784",
- A nonce ensuring that this JWT can be used only once
}
Signature:
RSA Private Key converted to PKCS#8 Private Key signed with RSA-256 hashing algorithm
Let’s discuss how to generate the JWT bearer token in CPI Groovy Script assuming CPI plays the cloud middleware between the requesting cloud tenant and target Customer Data Cloud.
First step is to upload the RSA Private Key to Keystore in CPI. The RSA private key is provided in SAP Customer Data Cloud console
Now let us create a simple integration flow which will accept a request and return the JWT Bearer Token. Assume the incoming request contains the user key and jti – JWT ID unique for each token request
Integration flow receives the incoming request and has a content modifier which reads the user key and jti and assigns it to exchange parameters
Next component is the integration flow is the Groovy Script which will read the RSA Private Key from the key store and encoded in base64 format. This will be converted to PKCS8 format and hashed with RSASHA256 algorithm for asymmetric encryption.
Declare the headers,
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import io.jsonwebtoken.*
import com.sap.it.api.ITApiFactory;
import java.util.Base64
import java.security.spec.PKCS8EncodedKeySpec
import com.sap.it.api.keystore.KeystoreService;
import java.security.KeyFactory
import java.security.Key;
import java.math.BigDecimal
//Get the timestamp with validity of 1 minute from now
def timeStamp = (System.currentTimeMillis() / 1000 + 60)
timeStamp = timeStamp.setScale(0, BigDecimal.ROUND_DOWN);
//Get the JWT ID and User key from exchange property
jwtid = message.getProperty("JWT_ID")
userKey = message.getProperty("UserKey")
Now formulate the jsonstring with the unique JWT ID and the timestamp generated
String jsonString = """{"iat":${timeStamp}, "jti":"${jwtid}"}""";
Read the RSA private key stored in security material
Key privateKey = keyService.getKey("rsa_private_key");
//Encode the key in Base 64
def base64EncodedPrivateKey = (Base64.getEncoder().encode(privateKey.getEncoded()));
Next step is to conver the base 64 encoded RSA private key to Public-Key Cryptography Standards (PKCS)#8
//Create an instance of PKCS8EncodedKeySpec with the given encoded key
def pkcs8KeySpec = new PKCS8EncodedKeySpec(base64EncodedPrivateKey)
//Get an instance of KeyFactory to convert keys to RSA algorithm
def pkcs8KeyFactory = KeyFactory.getInstance("RSA")
//Generate a private key in PKCS8 format
def pkcs8Key = pkcs8KeyFactory.generatePrivate(pkcs8KeySpec)
Call JWTBuilder API to generate a JSON Web Token signed with the PKCS8 private key
String jwtToken = Jwts.builder()
.setHeaderParam("typ","JWT")
.setHeaderParam("alg","RS256")
.setHeaderParam("kid",userKey)
.setPayload(jsonString)
.signWith(SignatureAlgorithm.RS256, pkcs8Key)
.compact();
//Assign the token to message body
message.body = jwtToken
Now, let’s test the integration flow from Postman. Call the integration flow and pass the jti – JWT ID and CDC user key in the payload
Integration flow returns the JSON Web Token which can be used to sign requests to Customer Data Cloud
The validity time of the JSON Web Token can be increased by changing the timestamp which is currently set to 60 seconds
//Get the timestamp of 1 minute from now
def timeStamp = (System.currentTimeMillis() / 1000 + 60)
timeStamp = timeStamp.setScale(0, BigDecimal.ROUND_DOWN);
In the next blog we will cover how to automate the generation of jti – unique JWT ID instead of passing it in the call request
Thanks for your time!
Regards, ArunKumar Balakrishnan