Skip to Content
Technical Articles

SAP CPI – Salesforce Rest API Integration using OAUTH JWT Bearer Flow – Part 2

In Part-1 of this blog we saw

  • How to setup trust between SAP CPI and Salesforce and
  • Implement Main Flow to consume Salesforce API.

In this blog let us see how to implement Integration flow to fetch access token using JWT Bearer Flow and update global variable.

Before we move into Integration Flow configuration lets us also understand the details of JWT Bearer implementation of Salesforce. 

Salesforce – Create JWT Bearer Token

  • Construct a JWT header with this format: {"alg":"RS256"}and encode it using Base64url
  • Construct a JSON Claims Set for the JWT with isssubaud, and exp and encode it using Base64url
{"iss": "<Client_ID>", 
"sub": "<my@email.com>", 
"aud": "https://login.salesforce.com", 
"exp": <expiration time of the assertion within 3 minutes>}
  • Create a string with format: encoded_JWT_Header + "." + encoded_JWT_Claims_Set
  • Sign the resulting string using SHA256 with RSA.
  • Create a string with format encoded_JWT_Header + "." + encoded_JWT_Claims_Set + "." + base64_encoded_signature 

Salesforce – Fetch Access token

Post the JWT Bearer Token constructed to https://login.salesforce.com/services/oauth2/token with below parameters

  • grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
  • assertion=JWT bearer token

After the request is verified, Salesforce responds with access token, instance URL etc.

Integration Flow Design

Now let us design Integration flow to construct JWT token that comply with format rules specified in RFC7519 and post it to Salesforce token endpoint. The iflow also implement logic to parse JSON response, retrieve and store access token and instance URL in global variables.

Limitations with standard Step Type

  • Base64 Encoder: Output text is not URL safe.
  • Simple Signer : The signature value computed is Base64 encoded and hence not URL safe.

Workaround

To overcome the limitation I have implemented Script to

  • Perfrom Base64url encoding using org.apache.commons.codec.binary.Base64 library 
  • Replace characters + and / with and _ respectively and remove = from signature to make it URL safe.

SAP should probably consider including URL safe encoding feature in CPI roadmap.

Step Type Name Description
Content Modifier
setHeader

Set Message Body to {“alg”:”RS256″}

Script b64URL1 Base64 URL Encode JWT header
Script
setReqProperty
  • Read SFDC client ID stored in Secure store and set it in property field “clientID”
  • Compute epoch timestamp and set in property field “expTime”
Content Modifier setClaimSet
  • Store encoded JWT header to exchange property “jwtheader”

  • Populate message body with JWT claimset

Script b64URL2 Base64 URL Encode the JWT claimset
Content Modifier constructToken

Append JWT Header and JWT Claimset with .(dot) delimiter

Note: All steps until here (6 totally) can be combined in to one using Groovy Script. For demonstration I have used standard step types.

Signer
SimpleSigner
  • Sign the message body with Private key created with alias “sfdctoken” (Part-1) using RSA SHA256 algorithm.
  • Store resulting signature in “JWTSignatureValue” header.

Script b64URL3 Make the Signature value in header URL safe using string replace operation.
Content Modifier appendSignature
  • Insert Content-type = application/x-www-form-urlencoded header and

  • Populate Body with the grant_type and JWT assertion

Content Modifier DeleteHeader

Remove all headers except content-type.

Request Reply callAuthServer

Invoke Salesforce token endpoint.

Upon successful JWT validation salesforce return bearer access token.

{
"access_token": "00Dxx00001gPL.39u",
"scope": "web openid api id",
"instance_url": "https://yourIns.salesforce.com",
"id": "https://yourIns.salesforce.com/id/000",
"token_type": "Bearer"
}

Script extractToken Parse JSON Response and extract access token and instance URL
Write Variable saveToken

Save access token, instance URL and timestamp in global variable

 

Base64url Encoding Script

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import org.apache.commons.codec.binary.Base64;

def Message processData(Message message) {
	
	def body = message.getBody();	
	message.setBody(Base64.encodeBase64URLSafeString(body.getBytes("UTF-8")));
	
	return message;
}

Set Required Property Script

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.Date;
import com.sap.it.api.ITApiFactory;
import com.sap.it.api.securestore.SecureStoreService;
import com.sap.it.api.securestore.UserCredential;


def Message processData(Message message) {  
  
  //Set expiry time property value
  Date d = new Date();  
  message.setProperty("expTime",((d.getTime() / 1000) + 180).intValue());
  
  //Read SFDC connected APP client ID 
  def service = ITApiFactory.getApi(SecureStoreService.class, null);
  def credential = service.getUserCredential("SFDCClientCred");
  String clientID = credential.getUsername();
  //Store client ID as property
  message.setProperty("clientID", clientID);
  
  return message;
}

Make Signature URL Safe Script

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;

def Message processData(Message message) {
	
	//Get Signature value from header
	map = message.getHeaders();
	signedContent = map.get("JWTSignatureValue");
	
	//Replace characters to make it URL safe
	signedContent = signedContent.replaceAll("\n","").replaceAll("\\+","-").replaceAll("/", "_").replaceAll("\\=","");
	
	//Set URL safe Signature in header
	message.setHeader("JWTSignatureValue", signedContent);
	
	return message;
}

 

Parse JSON Response and read values

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import groovy.json.*
def Message processData(Message message) {
    
    //Get Response Payload
    def body = message.getBody(String.class);
    
	//Parse JSON Payload
    def jsonSlurper = new JsonSlurper()
    def list = jsonSlurper.parseText(body)
	
	//Retrieve required values and store it in property
	message.setProperty("accesstkn",list.access_token.toString());
    message.setProperty("insURL",list.instance_url.toString());
	
    return message;
}

 

Testing

Below is the screenshot from SAP CPI Trace

Main Flow – Request : Session Validity calculated false and Token Flow invoked

Token Flow – Fetch Token HTTP Request : Header & Payload

Token Flow – Fetch Token HTTP Response : Payload

Main Flow – HTTP API Request : Header

Main Flow – HTTP API Response : Payload

 

2 Comments
You must be Logged on to comment or reply to a post.
  • Hi Santhosh,

    Nice Blog!

    Few days ago i also got one requirement where i had to use JWT and URL safe encoding to connect with iSHARE.

    Ended up writing multiple scripts, Seems was the only option in CPI.

    And I agree SAP should consider URL safe encoding feature in CPI.

    Keep up the good Work !

    Thank You

     

  • Hi Santosh

    This is rather interesting, how you use CPI to generate a JWT Token and then trigger this as a external call for OAUTH.

    I wish this was an out of the box feature – but nevertheless good to know how we can use CPI to generate JWT Tokens.

     

    Maybe it would be interesing to see how you can use CPI to authenticate a JWT token? 🙂

     

    Regards

    Bhavesh