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

RSA%20Private%20Key%20downloaded%20from%20CDC%20Console

RSA Private Key copied from CDC Console

Add%20RSA%20Private%20Key%20to%20Security%20Material

Add RSA Private Key to Security Material

RSA%20Private%20Key%20in%20Security%20Material

RSA Private Key in Security Material

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%20Flow

Integration Flow

Integration flow receives the incoming request and has a content modifier which reads the user key and jti and assigns it to exchange parameters

Content%20Modifier

Content Modifier

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

Call%20Integration%20Flow%20from%20Postman

Call Integration Flow from Postman

Integration flow returns the JSON Web Token which can be used to sign requests to Customer Data Cloud

Generated%20JSON%20Web%20Token

Generated JSON Web Token

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

 

Sara Sampaio

Sara Sampaio

Author Since: March 10, 2022

0 0 votes
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x