Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
santhosh_kumarv
Active Contributor
7,301
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



 
4 Comments
Labels in this area