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
iss
,sub
,aud
, andexp
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 |
|
Content Modifier | setClaimSet |
|
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 |
|
Script | b64URL3 | Make the Signature value in header URL safe using string replace operation. |
Content Modifier | appendSignature |
|
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. |
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
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
Incredible, very useful. Thanks for the content
Hi Santosh,
Thank you for detailed blog.
I have similar requirement and followed the steps provided in the blog. when I execute the flow I received the error as "unsupported grant type".
I tried to decode the JWT token using the jwt.io but I got an error as invalid signature. I have used the RSA512 alg to generate the keypair in CPI.
Please let know when I went wrong in the process.
Regards,
Jaya