Skip to Content

Introduction

Recently a question was raised here in the community how the secure store functionality can be used in a XS Advanced application without using the $.security.Store API available via the XSJS compatibility layer. Until HANA 2.0 SPS02 there was no official information available although the functionality was already part of the system, but not officially released, at least to my knowledge. With HANA 2.0 SPS03 in the SAP HANA Developer Guide for XS Advanced the Application Security chapter was enhanced by a new sub-chapter Maintain Values in the SAP HANA Secure Store which adds the information that the secure store can be used by new procedures:

  • SYS.USER_SECURESTORE_INSERT to insert new entries
  • SYS.USER_SECURESTORE_RETRIEVE to get a secure store entry
  • SYS.USER_SECURESTORE_DELETE to delete a secure store entry

As the documentation at this point did not give me – let me say – enough information (e.g. parameters and their meanings) and the examples are “just” for Java (also w/o further describing any details) I experimented a little bit with the “new” procedures within a XS Advanced NodeJS module on a SAP HANA Express Edition installation (based on HANA 2.0 SPS03).

Finding the interface information for the Secure Store procedures

As we know from the documentation we somehow have to call procedures to insert/retrieve/delete secure store values. So it would be good to know the interface of the procedures. At least somehow in the current HANA 2.0 SPS03 documentation that information is missing, because it is not really important I think (you recognize the irony). So where to find that information. Good that there exists a system view PROCEDURES which shows at least the technical details. Lets switch to the Database Explorer, connect to SystemDB and do the following query:

In the results DEFINITION column the interface definition can be found.

SYS.USER_SECURESTORE_INSERT

Parameter Direction Type Description
STORE_NAME IN NVARCHAR(530) The store name of the secure store.
FOR_XS_APPLICATIONUSER IN BOOLEAN A boolean to indicate if the value is stored for the application user instead of the technical user.
KEY IN NVARCHAR(1024) Key within the secure store.
VALUE IN VARBINARY(5000) Value of the secure story entry in binary.

 

SYS.USER_SECURESTORE_RETRIEVE

Parameter Direction
STORE_NAME IN
FOR_XS_APPLICATION_USER IN
KEY IN
VALUE OUT

 

SYS.USER_SECURESTORE_DELETE

Parameter Direction
STORE_NAME IN
FOR_XS_APPLICATION_USER IN
KEY IN

 

Using the Secure Store Procedures within a NodeJS module

As we now know some more details about the procedures we are going to use them within a NodeJS module of an XS Advanced Multi-Target application. Please consider that the following coding contains just quick and dirty examples, so code structuring/encapsulation/error handling/… were not in focus. In a real-world application you have to consider that, but of course you know that.

Create a HANA service instance with “securestore” plan

To be able to use the secure store an instance of the “hana” (or “managed-hana”) service has to be created with service plan “securestore”.

With the new XS Advanced Cockpit this can be done in a very easy way by going to the “hana” service -> Instances -> New Instance. I created one with the name “securestore_test-hana”. This service instance will be bound later to our NodeJS application.

If you wanna create the service via the XS command line tools you can do it like following (make sure to be in the right org and space):

xs cs hana securestore securestore_test-hana

Create a NodeJS module

Next step is to create a Multi-Target Application within the SAP Web IDE for SAP HANA using the project from template functionality. Within the created MTA a new node module is added (I called it node_securestore_test). To this node module a simple express application will be added with routes to interact with the secure store.

Prepare the development descriptor file (mta.yaml)

The resource “securestore_test-db” pointing to the before created hana securestore service needs to be added to the development descriptor file as resource. This resource is required by the NodeJS module.

For the test also an xsuaa service instance “securestore_test-uaa” was created, added as resource and added as required resource for the NodeJS module.

ID: xsa_securestore_test
_schema-version: '2.0'
version: 0.0.1

modules:
 - name: node_securestore_test
   type: nodejs
   path: node_securestore_test
   provides:
    - name: node_securestore_test_api
      properties:
         url: ${default-url}
   requires:
     - name: securestore_test-uaa
     - name: securestore_test-db
         
resources:
 - name: securestore_test-uaa
   type: com.sap.xs.uaa-space
   parameters:
     config_path: ./xs-security.json

 - name: securestore_test-db
   type: com.sap.xs.hana-securestore
   parameters:
     service-name: securestore_test-hana

Prepare the server.js file

Within the server.js file (created by the NodeJS module creation) an express app is created, the hana securestore instance is added as middleware to the express app and an HTTP server is started. To the express app the routes for the secure store tests, defined in the router folder, are added too (details are described in the next chapter).

"use strict";

// create express app
var app = require("express")();

// add secure store middleware to express app
var xsenv = require("@sap/xsenv");
var hdbext = require("@sap/hdbext");

var hanaOptions = xsenv.getServices({
	secureStore: {
		name: "securestore_test-hana"
	}
});

app.use(
	hdbext.middleware(hanaOptions.secureStore)	
);

// create server instance
var server = require("http").createServer();

// setup routes of express app
var router = require("./router")(app, server);

// start server
var port = process.env.PORT || 3000;
server.on("request", app);
server.listen(port, function() {
	console.info('HTTP Server: ${server.address().port}');
});

Prepare the express routes for inserting/retrieving/deleting a secure store entry

In this step the express routes and the endpoint logic to interact with the secure store is described.

First a folder “router” is created. In this folder a file “index.js” is created with the following content which describes the available routes. Within the “server.js” file (see above) that file is used by the “setup of routes” step.

The following routes are made available by the router implementation:

  • /createSecureStoreEntry to create a secure store entry
  • /retrieveSecureStoreEntry to retrieve the created secure store entry
  • /deleteSecureStoreEntry to delete the created secure store entry
"use strict";

module.exports = (app, server) => {
	app.use("/createSecureStoreEntry", require("./routes/createSecureStoreEntry")());
	app.use("/retrieveSecureStoreEntry", require("./routes/retrieveSecureStoreEntry")());
	app.use("/deleteSecureStoreEntry", require("./routes/deleteSecureStoreEntry")());
};

As it can be seen in the above router coding the route implementations are created in a new folder “routes” (created within the “router”) folder.

Creating a secure store entry

Within file “createSecureStoreEntry.js” with the module “@sap/hdbext” the procedure “SYS”.”USER_SECURESTORE_INSERT” is loaded and executed. As store name “TEST_STORE” is used, the used key is “TEST_VALUE” and the value itself is a dummy string converted to a binary.

"use strict";

module.exports = function() {
	var express = require("express");
	var hdbext = require("@sap/hdbext");
	var app = express.Router();
	
	app.get('/', function(req, res) {
		hdbext.loadProcedure(req.db, "SYS", "USER_SECURESTORE_INSERT", function(error, proc) {
			if(error) {
				res.send("Error during procedure loading:" + error.message);
				return;
			}
			
			proc({"STORE_NAME":"TEST_STORE", 
			      "FOR_XS_APPLICATIONUSER": false, 
			      "KEY": "TEST_VALUE", 
			      "VALUE": Buffer.from("Test Secure Store Value")}, function(error){
				if(error) {
					res.send("Error during procedure execution: " + error.message);
					return;
				}
				res.send("Entry in secure store successfully created.");	
			});
		});
	});

	return app;
};

Retrieving a secure store entry

For retrieving the created value in store “TEST_STORE” with key “TEST_VALUE” procedure “SYS”.”USER_SECURESTORE_RETRIEVE” is used. The value store as binary is converted back to a string value for the output.

"use strict";

module.exports = function() {
	var express = require("express");
	var hdbext = require("@sap/hdbext");
	var app = express.Router();
	
	app.get('/', function(req, res) {
		hdbext.loadProcedure(req.db, "SYS", "USER_SECURESTORE_RETRIEVE", function(error, proc) {
			if(error) {
				res.send("Error during procedure loading:" + error.message);
				return;
			}
			
			proc({"STORE_NAME":"TEST_STORE", 
			      "FOR_XS_APPLICATIONUSER": false, 
			      "KEY": "TEST_VALUE"}, function(error, out_parameters){
				if(error) {
					res.send("Error during procedure execution: " + error.message);
					return;
				}
				
				if(!out_parameters.hasOwnProperty("VALUE")) {
					res.send("Value of secure store entry could not be determined.");
					return;
				}

				res.send("Retrieved value: " + Buffer.from(out_parameters["VALUE"]).toString());	
			});
		});
	});

	return app;
};

Deleting a secure store entry

For the deletion of the secure store entry in store “TEST_STORE” with key “TEST_VALUE”, procedure “SYS”.”USER_SECURESTORE_DELETE” is called the same way than the other procedures.

"use strict";

module.exports = function() {
	var express = require("express");
	var hdbext = require("@sap/hdbext");
	var app = express.Router();
	
	app.get('/', function(req, res) {
		hdbext.loadProcedure(req.db, "SYS", "USER_SECURESTORE_DELETE", function(error, proc) {
			if(error) {
				res.send("Error during procedure loading:" + error.message);
				return;
			}
			
			proc({"STORE_NAME":"TEST_STORE", 
			      "FOR_XS_APPLICATIONUSER": false, 
			      "KEY": "TEST_VALUE"}, function(error){
				if(error) {
					res.send("Error during procedure execution: " + error.message);
					return;
				}

				res.send("Entry in secure store successfully deleted.");	
			});
		});
	});

	return app;
};

Build and run the NodeJS application

After the coding part is finished the MTA can be build and the NodeJS application/module can be executed.

In this case the NodeJS application is running on port 51026.

Executing the /createSecureStoreEntry results in following:

Executing the same route again results in an error, because of a duplicate key:

Lets retrieve the secure store entry by route /retrieveSecureStoreEntry:

And finally delete it using route /deleteSecureStoreEntry:

Conclusion

I hope this further insights are a little bit helpful until the documentation will maybe improved.

The code example can be found here at Github.

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply