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: 
ferrygun18
Contributor
In this quick tutorial, we'll learn how to create a SAP Analytics Cloud custom widget REST API with OAuth 2.0 and SAP HANA XSA.



Background


We have a scenario where user needs to get the scoring result from the R Plumber service. User will enter the partner number and expect to get the score result from SAC screen.

The R plumber service is a HTTP GET request without authentication. As we need to protect this service, one of the possible solution is to use OAuth 2 authentication between SAC and R plumber service. The HANA XSA comes in to the picture for the OAuth 2 authentication.

To fulfill the above requirements, we will write a code for the following parts:

  • SAC custom widget, restAPI.

  • SAP HANA XSA (NodeJS and Web module), zrestapi.


The R plumber service is out of scope of this blog.

SAC Custom Widget


We will create an user interface like below. There are two UI elements, textarea and button. The textarea under Result is coming from the actual SAC widget.

Everything is happening under onButtonPress() function. The first thing is to get the access token with the client_id and client_secret. We will get these values from HANA XSA later. Once we got the token, then we can perform the HTTP Post request to get the scoring result.



onButtonPress: function(oEvent) {
var this_ = this;

var partnernumber = oView.byId("input").getValue();
console.log(partnernumber);
this_.wasteTime();

var CLIENT_ID_str = 'REPLACE_WITH_CLIENT_ID';
var CLIENT_SECRET_str = 'REPLACE_WITH_CLIENT_SECRET';

$.ajax({
type: 'POST',
url: "https://REPLACE_WITH_TOKEN_URL/uaa-security/oauth/token",
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
crossDomain: true,
cache: true,
dataType: 'json',
data: {
client_id: CLIENT_ID_str,
client_secret: CLIENT_SECRET_str,
grant_type: 'client_credentials',
},

success: function(data) {
console.log(data);

var access_token = data.access_token;

$.ajax({
url: restAPIURL,
type: 'POST',
headers: {
"Authorization": "Bearer " + access_token,
"Content-Type": "application/x-www-form-urlencoded"
},
data: $.param({
"partnernumber": partnernumber
}),
async: true,
timeout: 0,
contentType: 'application/x-www-form-urlencoded',
success: function(data) {
this_.runNext();
console.log(data);
_score = data;

that._firePropertiesChanged();
this.settings = {};
this.settings.score = "";

that.dispatchEvent(new CustomEvent("onStart", {
detail: {
settings: this.settings
}
}));

},
error: function(e) {
this_.runNext();
console.log("error: " + e);
console.log(e);
}
});

},
error: function(e) {
this_.runNext();
console.log(e.responseText);
}
});
},

We'll get the Client ID & Secret and Token URL from HANA XSA.

SAP HANA XSA



  • Create SAP Cloud Platform Business Application in SAP HANA XSA, zrestapi.

  • Create the NodeJS module. Replace the server.js with the code below.


/*eslint no-console: 0, no-unused-vars: 0, no-undef:0, no-process-exit:0*/
/*eslint-env node, es6 */
"use strict";
const port = process.env.PORT || 3000;
const server = require("http").createServer();

//Initialize Express App for XSA UAA and HDBEXT Middleware
const xsenv = require("@sap/xsenv");
const passport = require("passport");
const xssec = require("@sap/xssec");
const express = require("express");
global.__base = __dirname + "/";

const https = require('https');
const http = require('http');
const cors = require('cors');
const querystring = require('querystring');

//logging
var logging = require("@sap/logging");
var appContext = logging.createAppContext()

//Initialize Express App for XS UAA and HDBEXT Middleware
var app = express();

app.use(cors());
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*"); // update to match the domain you will make the request from
res.header('Access-Control-Allow-Methods: OPTIONS,GET,PUT,POST,DELETE');
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});

var bodyParser = require('body-parser');
app.use(bodyParser.json()); // support json encoded bodies
app.use(bodyParser.urlencoded({
extended: true
})); // support encoded bodies

//Compression
app.use(require("compression")({
threshold: "1b"
}));

//Helmet for Security Policy Headers
const helmet = require("helmet");
// ...
app.use(helmet());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "sapui5.hana.ondemand.com"],
scriptSrc: ["'self'", "sapui5.hana.ondemand.com"]
}
}));
// Sets "Referrer-Policy: no-referrer".
app.use(helmet.referrerPolicy({ policy: "no-referrer" }));

passport.use("JWT", new xssec.JWTStrategy(xsenv.getServices({
uaa: {
tag: "xsuaa"
}
}).uaa));
app.use(logging.middleware({
appContext: appContext,
logNetwork: true
}));
app.use(passport.initialize());
app.use(
passport.authenticate("JWT", {
session: false
})
//xsHDBConn.middleware(hanaOptions.hana)
);

var corsOptions = {
origin: '*',
optionsSuccessStatus: 200
}


// Redirect any to service root
app.get("/node", (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/html'
});
res.write('OK');
res.end();
});

app.post('/score', function(req, res) {
req.setTimeout(0);
console.log(req.body.partnernumber);

async function getPartnerNumber() {
let response = await doRequest(req.body.partnernumber);
console.log(response);
res.status(200).send(response)
}
getPartnerNumber();
})

function doRequest(partnernumber) {
console.log();
return new Promise((resolve, reject) => {

var post_data = querystring.stringify({
'partnernumber': partnernumber
});

// An object of options to indicate where to post to
var post_options = {
host: 'R_PLUMBER_SERVER',
port: 'R_PLUMBER_PORT',
path: '/score',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(post_data)
}
};

var body = '';
var post_req = http.request(post_options, function(res) {
res.setEncoding('utf8');
res.on('data', function(chunk) {
console.log('Response: ' + chunk);
body += chunk;
});
res.on('end', function() {
resolve(body);
});
});

post_req.write(post_data)
post_req.end();
});
}

//Start the Server
server.on("request", app);

//Start the Server
server.listen(port, function () {
console.info(`HTTP Server: ${server.address().port}`);
});


  • Create Web module and other artifacts like xs-app.json and xs-security.json.

  • Run the both NodeJS and Web modules.

  • If you try to click the link of the NodeJS server URL, you will get unauthorized message, which is correct.  We need to have the access token first as mentioned earlier.

  • Now let's get the client ID, secret and token URL in order to generate the token. Go to Tools > SAP HANA XS Advanced Cockpit.

  • Search the app and select the Environment Variables. Under VCAP_SERVICES, get the clientid, clientsecret and url.

  • Update the code in SAC custom widget with clientid, clientsecret and tokenurl.
    var CLIENT_ID_str = 'REPLACE_WITH_CLIENT_ID';
    var CLIENT_SECRET_str = 'REPLACE_WITH_CLIENT_SECRET';

    url: "https://REPLACE_WITH_TOKEN_URL/uaa-security/oauth/token"​



Usage



  • Insert the custom widget restAPI.

  • Create the layout in SAC Analytic Application.

  • On the onStart() event, put the below code. This is to get the scoring value and print in TextArea_1.
    var score = restAPI_1.getScore();
    console.log("score: " + score);
    TextArea_1.setValue(score);​


  • Under styling of restAPI widget, fill in the REST API URL with the SAP HANA XSA NodeJS server URL and put any name under Widget Name.

  • Save and run the application.


Demo Video



References


2 Comments
Labels in this area