Technical Articles
SAP HANA XSA pt. 1 – CDS, HDI and XSUAA
Introduction
Summer is the time for vacation! Here in Japan is the tradition of Obon in August which is a Buddhist festival to commemorate the ancestors and travel the country to meet the family and enjoy a good time.
This year everywhere in the world all is different: Instead of travelling the country many stay at home or in the closer surrounding. I think it could be an opportunity to play and try out new things in software! For those of you that feel the same I want to initiate a tiny blog series around SAP HANA XSA development. It’s aiming to those that have already mastered first steps and want to explore further.
Ideally, it should save you hours of time of exploration later and give you a jump-start to get your own projects done.
From the perspective of the blog series I’ll build it around the flight data application (good old R/3 world) but completely HANA XSA based. In fact, we’ll build upon the Cloud Application Programming model.
What I plan to cover:
- XSUAA authentication and HDI handling from node.js and CDS
- SAPUI5 SmartTable with OData and Smart Variant Management
- UI5 as a service and Fiori Launchpad Site with dynamic tiles
- Accessing OData services from S/4HANA through node.js
- … continued if there is interest/ time
Requirements
You need access to a recent HANA XSA system – the free SAP HANA Express will work perfect for you. You should be familiar with the WebIDE, Git, node.js and UI5 on beginner level. I will provide the repository on Github, so you can play with the (working) code once you read through the text.
Let’s get started!
Enough said, let’s get hands-on! In this part of the blog we will build the project, add a DB module, two node modules and a UI5 module for authentication.
We look into a few things:
- How to define a database table by CDS
- How to create an OData v4 service by CDS
- How authentication and authorization work with CDS-declared modules
- How to access the same database table from a different node.js module
- How non-CDS node.js modules use authentication and authorization
The source code can be obtained from this repository. Enjoy!
What the heck is CDS?
When I started to work with SAP HANA I considered CDS as “Core Data Services” which are defined by creating a .hdbcds-file. Sometime later I discovered that this can’t be the whole story as I saw .cds-files generated by a sample project in SAP HANA XSA which by a few lines of simple instructions could do wonderful things. Things that that previously needed many lines of code: table and view declarations and even OData services!
The documentation of the “secret sauce” can be found in the reference documentation. Ah! And now we know this is all part of the SAP Cloud Application Programming model (CAP). In short: You can write object notations and the CDS compiler will generate all the artifacts which one had to program manually before. Nice! The application in this blog will be based on this model where possible.
Our project structure and the CDS artifacts
SAP WebIDE – Project structure
As you see from the outline above, we have a db, an html5 and a node.js module as part of our little project. As you know the node_modules folder is generated and should be ignored. We will be using data from the classic flight db of SAP Netweaver ABAP.
Two files are mainly responsible for all the content for the moment in our project:
- flight-model.cds as part of the db-module
namespace flightModel; define entity Customer { key CustomerNumber : Integer; CustomerName : String(25); AddressForm : String(15); Street : String(30); POBox : String(10); PostalCode : String(10); City : String(25); CountryCode : String(3); Region : String(3); Phone : String(30); CustomerType : String(1); DiscountRate : Integer; LanguageKey : String(1); Email : String(40); WebUserName : String(25); }
- flight-model-service.cds as part of the srv-module.
using { flightModel } from '../db/flight-model'; service flightService{ entity Customers @readonly as projection on flightModel.Customer; }
The first describes the table entity, and by the second (one line of code!) the table will be projected as an OData v4 service. How convenient! You might recognize, that every time you make a change in a CDS file and save it, the CDS compiler is triggered and the generated artifacts are updated.
First hint: I found it mandatory at times to have the script delete all previous generated artifacts. For that you need to change the package.json on project root level:
"scripts": {
"build": "cds build/all --clean",
"deploy": "cds deploy",
"start": "cds run"
},
add the –clean parameter to it.
Now we run the srv-module and see if the OData-Service works:
CDS Services menu
Looks good! Now we click on Customers and should see:
Table data provided by OData Service
Great! Now instead of consuming the data right away we recognize that we didn’t get prompted for a username and password. That’s ok for testing but how to secure the application? Particularly how to secure this CDS-generate service?
Securing a CDS-compiled service for authentication
Let’s enhance our flight-model-service.cds so that it looks like this:
using { flightModel } from '../db/flight-model';
service flightService @(requires: 'authenticated-user'){
entity Customers @readonly as projection on flightModel.Customer;
}
Let’s also add a few lines to package.json which is inside the srv module (not the one on root level!), you would need to add the last 3 lines:
{
"name": "hanaXsaTopics-srv",
"description": "CDS - Services package.json",
"version": "1.0.0",
"dependencies": {
"@sap/cds": "3.21.1",
"express": "^4.17.1",
"@sap/hana-client": "^2.5.104",
"passport": "^0.4.1",
"@sap/xssec": "^2.1.17",
"@sap/xsenv": "^3.0.0"
},
...
Passport is a security module that can handle all kinds of authentication standards, xssec is for the xsuaa specific handling, xsenv is to provide xs-specific environment variable data. More concise information can be found in the SAP Help.
Let’s run it and see what happens, we get the cds.services menu and click on Customers and… a pop-up appears to ask for username and password!
Chrome prompts for username/password
Now, no matter how right or wrong the user/password is – Sesame won’t open for us. We get some logs though:
Therefore the entry is recognized! Ultimately the bouncer tells us the bad news:
No party for us tonight!
Obviously we miss some settings… Of course! xsuaa must be added to the mta.yaml. Also we now need our simpleUi5 module to allow for a login screen. Very likely you have done xsuaa authentication in your apps before. If you have not, check the mta.yaml – you will see that the ui5 module provides a reference and the node.js srv-module requires this destination. So when we log in to xsuaa the authorization token is passed on to the srv-module. By the means of a JSON-Web-Token (JWT) this information is transmitted.
Aha! We have to inform our srv-module we speak JWT – no need for browser popups like above!
Second hint: This must be added to the package.json of the srv-module and I believe this piece of information is hard to find as the documentation does not mention it necessary:
"cds": {
"requires": {
"db": {
"kind": "hana",
"model": "gen/csn.json"
}
},
"auth": {
"passport": {
"strategy": "JWT"
}
}
}
Add the auth:-part to the file as shown above.
Lastly, adjust the xs-app.json to enable the forwarding from the simpleUi5-module to the service like that:
{
"welcomeFile": "webapp/index.html",
"authenticationMethod": "route",
"routes": [{
"source": "^/customerodata/(.*)$",
"target": "/flight/$1",
"destination": "srv_api",
"csrfProtection": false,
"authenticationType": "xsuaa"
},{
"source": "/(.*)",
"localDir": "resources",
"authenticationType": "xsuaa",
"replace": {
"pathSuffixes": [
"index.html"
]
}
}]
}
Above shows the full xs-app.json – so with this we should be set! Let’s start again and check out our OData-Service. When we start the simpleUi5 we will get the login screen:
OData Service with authentication
Yay, first step mastered, we now have user authentication with a CDS service.
Authorization with CDS services
Now let us look into authorization – we want to check the user master and allow certain views only if the right scope and attribute(s) are given.
For that we need to create a xs-security.json file which you can find also in the provided example. Let’s add the checks to our CDS service and for that we introduce CDS aspects. They help us to separate in our case service logic from authentication and authorization logic. For that we rewind the changes in flight-model-service.cds and create a new file on the same level called flight-model-service-auth.cds. The name doesn’t matter.
the old cds file has become simple again:
using { flightModel } from '../db/flight-model';
service flightService{
entity Customers @readonly as projection on flightModel.Customer;
}
and the new file looks like this:
using { flightService } from './flight-model-service';
// extend the previously defined service by authentication check
annotate flightService with @(requires: 'authenticated-user');
// extend the entity by authorization check
annotate flightService.Customers with @(restrict: [
{ grant: 'READ', to: 'Display' }
]);
You see, we extend the logic by an aspect and by that can now add e.g. OData specific annotations to the previous file without mixing them with authorization checks.
If we run the service again, we will now face a new error due to the missing Display authorization in our user master:
To overcome that error, we must do the following:
- Create a Role Collection – we’ll do it in the SAP HANA XSA Cockpit:
In our example I created a role collection named ZXSA_TOPICS.
- In the Cockpit navigate to the application (e.g. our srv-module) and create a new role:
then, add the new role to our role collection:
- Lastly create a new user for testing and assign the role collection to the user.
In our example I named the user XSATOPICS.
Third hint: The scopes are – like most objects – case sensitive. So e.g. make sure the check in the CDS uses Display if the scope in xs-security.json was Display and not display.
You will now see the list of customers when you execute the service with your new user:
Checking user attributes in CDS
As a last exercise for CDS let’s check for the user’s assigned country attribute. You might have recognized in the above screenshot I assigned the static attribute ‘US’ to the role (and also named the role accordingly).
The only thing to do in our flight-model-service-auth.cds is below line change:
using { flightService } from './flight-model-service';
// extend the previously defined service by authentication check
annotate flightService with @(requires: 'authenticated-user');
// extend the entity by authorization check on scope and attribute
annotate flightService.Customers with @(restrict: [
{ grant: 'READ', to: 'Display', where: 'CountryCode = $user.country'}
]);
We restart the service and – wow! – by magic of CDS we can only see US-based customers in the list. Great!
Filtered OData Service output by country attribute authorization check
Non-CDS access to HDI containers
The last topic of this blog is to answer the question how to access the HDI container and run authentication/authorization checks in a classic node.js module.
For that we have extended our project by a new node.js module called coreNode. We will continue to build on this module in the coming blogs hence the name.
A first look should be into the server.js which loads modules which we have already seen in the CDS example. This time – however – we make use of express‘ middleware ability to add authentication/authorization data from passport (and xssec) and SAP HANA DB access by hdbext. Middleware is added with the keyword .use in express:
app.use(passport.authenticate('JWT', {
session: false
}),
xsHDBConn.middleware(hanaOptionsReduced)
); // Add hdb handler
Using the setup of express we can direct every http-request coming into an according sub-module as you can see from the folder structure by the express-router. So even for large projects node.js-activities can be centralized. This helps to keep the overview due to the hierarchical structure and avoids duplicate coding.
Using the express router to segregate node capabilities
In our first part of this blog series we just have one tutorial.js, we will extend it later with additional modules. When you look in the index.js you find the route to it:
app.use('/tutorial/', require('./routes/tutorial')());
Now we will mimic the same reply like from the CDS service in native node.js. Have a look into the tutorial.js – the code should be self-explanatory. Note, how we also make use of the role-attribute country which is assigned to the user.
Note: this route will not provide OData related functions, it just outputs the db-data in JSON format!
To be able to reach this service we have extended the xs-app.json of our simpleUi5-module once more and according settings were made in the mta.yaml. Therefore, by calling the service-route from the browser by
https://<your hana url>/access/tutorial/demo
a similar output will be given. To close, I want to show you how authorization and and authentication as well as DB access is embedded in every http-request from the debugger session:
http-request containing JWT info and HANA DB connection
Opening the security context exposes all data available for the current user:
authInfo from http-request in node.js debugging session
Closing
I hope the information in this first blog was useful to clarify authentication, authorization in CDS and node.js environments in the context of accessing the SAP HANA database. Thank you for reading!
Do you have any suggestions or requests? What did you find challenging working with CDS, CAP or XSA in general? Leave your comments! ?
Very clear and welll explained