Recently I was involved in a project of web application using SAPUI5 (which I really hate) and in that, I needed to call AWS Machine learning service to predict some values using a model which was already been deployed on the AWS. As you may know, Call from the AWS CLI is easy cause it is already authenticated and just need to send the parameters as payload and it’ll respond a json object which includes the estimation of the model.
But I needed this values in my SAPUI5 app and if you ever work with that you can understand that it’s a very close solution and it just have some already implemented elements and some 3rd party libraries which are included. So even importing a library is a disaster.
Anyway, I needed to make a call to AWS Machine Learning Service and I needed to sign the call with SigV4. Basically, these are the Amazon documentation for API call to machine learning and how to sign the request and they are completely straightforward:

    Predict API : There’s request example in that
    Signing the AWS request : It contains for steps for building and making the request

So the example of request is like this:

POST / HTTP/1.1
Host: <hostname from the GetMLModel response EndpointUrl object>
x-amz-Date: <Date>
Authorization: AWS4-HMAC-SHA256 Credential=<Credential>, SignedHeaders=contenttype;date;host;user-agent;x-amz-date;x-amz-target;x-amzn-requestid,Signature=<Signature>
User-Agent: <UserAgentString>
Content-Type: application/x-amz-json-1.1
Content-Length: <PayloadSizeBytes>
Connection: Keep-Alive
X-Amz-Target: AmazonML_20141212.Predict
{"MLModelId" : "exampleMLModelId",
 "Record" : {
   "ExampleData" : "exampleValue"
 },
 "PredictEndpoint" : "<realtime endpoint from Amazon Machine Learning for exampleMLModelId>"
}

Note: you can test your call with postman since it has a special part for AWS and it automatically sign your request using the client code and secret provided by IAM service.

Before doing anything, for signing a AWS request with SigV4 you need to have an IAM account which has access to the needed services then inside the user there is a “Security credentials” tab and in that you can create the access key. As soon as you create the access key, copy the key and secret some where so you can use it later.

As it is explained in the documentation for signing the request, it has 4 steps and in every step we hash one part of the call and at the end we need to sign all of them with the signature that we create with “secret key”. Then we send them all with our “key ID”. (you can see that in the request example there is a place for credential and it is indeed the key ID that we send)
But the problem is how to hash the payload in the SapUI5? how to make the signature?
in the JS documentation of AWS it recommends “Crypto” library to use so let’s use it. We need to import it somehow in the sapui5 project.

Importing 3rd party library in sapui5 project

I can say this was very tricky since I waste couple of hours for this to trying different ways and at the end I found it and I decided to write it because honestly I found nothing on Internet (not a surprise for damn SAP, it is dark and sad…)
So the normal solution (for humans) is that they use npm to install cryptojs library, I made a dummy nodejs project and did that so I had the source in my “node_modules” folder. Then I made a zip file from them and import it inside the project folder on SAPUI5 IDE. sapui5-crypto
then I need to somehow use it in my controller right? I do this (which is weird)
in first line of my controller file:

/* global CryptoJS:true */

then I import it like a normal library:

/* global CryptoJS:true */
sap.ui.define([
	"sap/ui/core/mvc/Controller",
	"sap/m/MessageToast",
	"sap/ui/model/json/JSONModel",
	"spsml/model/models",
	'sap/m/Button',
	'sap/m/Dialog',
	'sap/m/Text',
	'spsml/libs/crypto-js/crypto-js',
	'sap/ui/commons/layout/VerticalLayout',
	'sap/m/OverflowToolbar'
], function(Controller, MessageToast, JSONModel, Model, Button, Dialog, Text, Cryptojs , Layout , OverflowToolbar) {

so now I can use it a “CryptoJS” not “Cryptojs”!!!

I prepared the function to get the signature, as already it is documented in AWS:

getSignatureKey: function(Crypto, key, dateStamp, regionName, serviceName) {
	var kDate = Crypto.HmacSHA256(dateStamp, "AWS4" + key);
	var kRegion = Crypto.HmacSHA256(regionName, kDate);
	var kService = Crypto.HmacSHA256(serviceName, kRegion);
	var kSigning = Crypto.HmacSHA256("aws4_request", kService);
	return kSigning;
}

Then I implement my call, in that I first get the time in the required format and use it in multiple places, then I get my signature. Afterwards I start to hash everything as it has explained in AWS documentation.
note: attention to $$ part which you should replace your variables

//making timestamp and datestamp
var now = new Date();
var timeStamp = now.toISOString().replace(/-/g, "").replace(/:/g, "").split('.')[0] + "Z";
var dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
//making payload
payload =
	'{"MLModelId": "$$Your Model ID$$" ,"PredictEndpoint": "https://realtime.machinelearning.eu-west-1.amazonaws.com" , "Record" : ' +
	JSON.stringify($$Your Parameters data to Send to Macine Learning$$) + ' }';
// hashing payload
var hashPayload = CryptoJS.SHA256(payload);
// getting signature to sign
var signatureKey = this.getSignatureKey(CryptoJS, "$$PUT your Secret key here$$", dateStamp, "$$Your Region$$",
	"machinelearning");

// making canonical string
var canonicalString = "POST\n/\n\ncontent-length:" +
	payload.length + "\ncontent-type:application/x-amz-json-1.1\nhost:realtime.machinelearning.eu-west-1.amazonaws.com\nx-amz-date:" +
	timeStamp + "\nx-amz-target:AmazonML_20141212.Predict\n\ncontent-length;content-type;host;x-amz-date;x-amz-target\n" +
	hashPayload.toString();
var hashCanonical = CryptoJS.SHA256(canonicalString);
// making string to sign
var StringToSign = "AWS4-HMAC-SHA256\n" +
	timeStamp + "\n" +
	dateStamp + "/$$your region$$/machinelearning/aws4_request\n" +
	hashCanonical.toString();
// Signing the the string to sign with the signature
var signature = CryptoJS.HmacSHA256(StringToSign, signatureKey);

// Calling the service
jQuery.ajax({
	type: "POST",
	contentType: "application/x-amz-json-1.1",
	headers: {
		"Authorization": "AWS4-HMAC-SHA256 Credential=$$Your Key ID$$/" +
			dateStamp +
			"/eu-west-1/machinelearning/aws4_request, 
                SignedHeaders=content-length;content-type;host;x-amz-date;x-amz-
                target, Signature=" +
	        signature,
		"Content-Type": "application/x-amz-json-1.1",
		"Host": "realtime.machinelearning.eu-west-1.amazonaws.com",
		"X-Amz-Target": "AmazonML_20141212.Predict",
		"X-Amz-Date": timeStamp,
		"Content-Length": payload.length
	},
	data: payload,
	url: "https://realtime.machinelearning.eu-west-1.amazonaws.com",
	success: function(data, textStatus, jqXHR) {
		jQuery.sap.log.info("aws prediction : " + 
                data.Prediction.predictedValue );
	}.bind(this),
	error: function(error) {
		jQuery.sap.log.error("error in http post reading: " + error);
	}.bind(this)
});

I really hope you never work with SAPUI5 but if one day you were obliged to do that and you need to import a library or use AWS services inside, here is the solution.

Leave a comment