Skip to Content
Technical Articles
Author's profile photo Dipenkumar Khamar

SAP Cloud Integration: Bloomberg API Integration using JWT OAuth Authentication.

Introduction:

Hello Integration Experts!!

I would glad to let you know that recently we had a business requirement where we need to Integrate with the Bloomberg to retrieve the daily exchange rates. Bloomberg is the global leader in business and financial datahttps://www.bloomberg.com/

Bloomberg supports Webservices, SFTP, REST API for Integration. We are using REST API with JWT (JSON Web Token) Oath authentication as latest, secure, and recommended approach by Bloomberg. This blog explains how you can Integrate SAP CI with the Bloomberg API with JWT signed by ClientSecret.

Use Case:

You might have created JWT and sign it using the X509 certificate’s Private key created and assigned with “SimpleSigner” pallet function of SAP CI.

But in this use case, we did not have that option to use private key pair and share the public certificate to Bloomberg to verify the signature.

Instead, we explored how you can implement the Signature by a valid ClientSecret. you can use the ClientSecret shared by Bloomberg and Create the Signature using HMAC SHA256 algorithm to generate the JWT and get authenticated with Bloomberg. Each request to Bloomberg API must include the unique JWT.

In this blog. I will explain how to create the JWT, Sign it by ClientSecret using the HMAC SHA256 and get authenticated with Bloomberg.

So, let’s get started. 🙂  Before that just wanted to Thank my friend saurabh sharma Nitin Pandey Nawaz Shareef who also contributed to explore on the same. Cheers 🙂

Prerequisites:

  1. Register your application, whitelist the IPs with Bloomberg and generate the ClientId, ClientSecret.
  2. Use the generated ClientSecret in sap CI to create the Signature.

Process steps:

JWT is a special JSON object with signature for verification that the request comes from the trusted source. JWTs Include a header, Payload with claim set and signature signed by a valid credential (ClientSecret).

The JWT is divided into three base64url encoded parts separated by. (dot) character.

  • JWT Header
  • JWT Payload
  • JWT Signature

Check here: https://jwt.io/

We can generate the JWT token by implementing the signature using HMAC SHA256 algorithm by using the Groovy Script in SAP CI.

  • Create a JWT header with this format: {“alg”:”HS256″,”typ”:”JWT”} and encode it using the Base64.
  • Create a JSON Claims Set for the JWT with iat, exp, nbf, iss, method, path, host, request_id, region and encode it using Base64.
  • Concatenate JWT Header and JWT Claim by dot (.) character and define as unsigned token.
  • Sign the resulting string using HMAC SHA256 by a valid Credential (ClientSecret) and encode the signature using Base64.
  • Convert the ClientSecret hex string to byte array before passing the same into HMAC SHA256 method of groovy.
  • Concatenate the unsigned token and signature by dot(.) which is nothing but the JWT to authenticate with Bloomberg.

Sample CI Integration Flow

you can declare the parameters in Exchange Property of Content Modifier as shown below.

Groovy Script for Generating the JWT

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;

def Message processData(Message message) {
    
    //Properties 
    map = message.getProperties();
    def clientSecret = map.get("clientSecret");
    def clientId = map.get("clientId");
    
    //convert Hex String to Byte array
    def clientSecretBlob = hexStringToByteArray(clientSecret);
   
    // Prepare JWT Header
    def jwtHeader = JWTHeader()
    
    // Prepare JWT JWTClaim
    def jwtClaim = JWTClaim()
    
    // Concat JWT Header and JWT Claim
    def unsignedToken= "${jwtHeader}" + "." + "${jwtClaim}";
    
    //JWT Signature
    def hash = hmac_sha256(clientSecretBlob, unsignedToken)
    signature = hash.encodeBase64().toString().replaceAll("\n","").replaceAll("\\+","-").replaceAll("/", "_").replaceAll("\\=","");
    
    //Headers
    def content = "application/json";
    message.setHeader("Content-Type", content);
    message.setHeader("api-version", 2);
    message.setHeader("jwt", unsignedToken + "." + signature);

    return message;
}

def JWTHeader() {
    def jwtHeader = """
            {"alg":"HS256","typ":"JWT"}
            """
    def jwtHeader_str = Base64.encodeBase64URLSafeString(jwtHeader.replaceAll("\\s","").getBytes("UTF-8"));

    return jwtHeader_str;
}

def JWTClaim() {
    def iat = (int)(new Date().getTime() / 1000);
    def exp = (int)((new Date().getTime() / 1000) + 30);
    def nbf = (int)(new Date().getTime() / 1000);
    def iss = map.get("clientId");
    def method = map.get("method");
    def path = map.get("path");
    def host = map.get("host");
    def request_id = UUID.randomUUID().toString();
    def region = map.get("region");
    def jwtClaim = """
            {"exp":${exp},"iat":${iat},"nbf":${nbf},"iss":"${iss}","method":"${method}","path":"${path}","host":"${host}","request_id":"${request_id}","region":"${region}"}
            """
    def jwtClaim_str = Base64.encodeBase64URLSafeString(jwtClaim.replaceAll("\n","").replaceAll("\\\\","/").getBytes("UTF-8"));
    
    return jwtClaim_str;
}

def hexStringToByteArray(String s){
    def len = (int) s.length();
    byte[] data = new byte[len / 2];
    for (int i = 0; i < len; i += 2) {
        int j = i / 2;
        String byte_hex = s.substring(i, i + 2);
        try {
            int byte_value = Integer.parseInt(byte_hex, 16);
            data[j] = (byte) byte_value;
        } catch (NumberFormatException error) {
            throw new IllegalArgumentException("Bad hex byte value " + byte_hex);
        }
    }
    return data;
}
def hmac_sha256(secretKey, String data) {
    try {
         Mac mac = Mac.getInstance("HmacSHA256")
         SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, "HmacSHA256")
         mac.init(secretKeySpec)
         byte[] digest = mac.doFinal(data.getBytes())
         return digest
        } catch (InvalidKeyException e) {
        throw new RuntimeException("Invalid key exception while converting to HMac SHA256")
    }
   }

 

Testing

Providing the screenshot from the SAP CI Trace.

 

Conclusion:

That’s how we successfully authenticated with Bloomberg using JWT signed by ClientSecret using HMAC SHA256 implementing the Groovy Script in SAP CI.

Hope this blog is useful to you. let me know your feedback and comments.

Thank you!!

Regards,

Dipenkumar Khamar

Assigned Tags

      7 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo VARRI NAIDU
      VARRI NAIDU

      Hi Dipenkumar,

      Thanks for sharing blog.

      I have also same requirement with ExchangeRate integration Bloomberg.

      I am getting below error  while posting JWT token to REST API  for fetching currency CSV data. Can you please check and help on that.

      org.apache.camel.component.ahc.AhcOperationFailedException: HTTP operation failed invoking url with 404.

      Awaiting for your kind response.

       

      Regards,

      Naidu

      Naidu.varri7@gmail.com

      Can you please help to

      Author's profile photo Dipenkumar Khamar
      Dipenkumar Khamar
      Blog Post Author

      Hi Naidu,

      It seems you are getting http "404" error which means "resource not found in Bloomberg".

      I would suggest to check on the datasets created in Bloomberg and corresponding "API URL" is correct for the same.

      Also verify if the Trigger is configure against the datasets created in Bloomberg for which you are requesting the CSV data.

       

      Regards,

      Dipenkumar Khamar

      Author's profile photo VARRI NAIDU
      VARRI NAIDU

      Hi Dipenkumar,

       

      Thanks for your quick response.

      W have tested API URL in postman and it working fine ,getting CSV data in response with status code 200.

      I have maintained all parameters in contentmodfier and after that getting invalid path parameter.

      we defined path value as  after hostname of URL in parameter value.

      Can you please check and suggest us which path need to maintain in parameter value.

       

      ,"errorCode":"bad-request","status":400,"detail":"This JWT has invalid path parameter"}],"error_description":"This JWT has invalid path parameter","error":"invalid_request"}

      Contentmodifier parameters detials:

       

      Regards,

      Naidu

      Email : naidu.varri7@gmail.com

      Author's profile photo Dipenkumar Khamar
      Dipenkumar Khamar
      Blog Post Author

      Hi Naidu,

      These Content modifier parameters would be assigned to your JWT Claim.

      Remove "https:// " from the host parameter value.

      Regards,

      Dipenkumar Khamar

      Author's profile photo VARRI NAIDU
      VARRI NAIDU

      Hi Dipen,

      Thanks for your support.

      Now am able to getting CSV data in response.

       

      Regards,

      Naidu

      Author's profile photo Kevin Hu
      Kevin Hu

      Thanks for sharing.

      Any reason the btp destination service cannot be used to generate the JWT in this case?

      Author's profile photo Dipenkumar Khamar
      Dipenkumar Khamar
      Blog Post Author

      Hi Kevin,

      Bloomberg does not support Token URL and Certificate for signing which is needed to configure the destination service.

      Moreover we need custom JWT based on Bloomberg formatted payload and signed by Valid Client Secret using HMAC SHA256.

      Regards,

      Dipen Khamar