Technical Articles
HANA XSA: Securing your application, and ensuring a good User Experience (UX) – Part 1
Is User Experience or Security more important?
In short, both are equally important. One of the most overlooked areas of user experience is security. As with most user-oriented design, security should be implemented in a way that guides the user, only allowing them to interact in ways which they are authorized. on the other hand, security should not force the user to have a bad experience, it should enhance it.
Out-of-the-box methods for securing an application often leaves the user with a bad experience.
This series of blog posts will guide you through some common scenarios where a little bit of effort can lead to a much better user experience. There will be some additional posts to cover the scenarios below, with example code for each.
Stay tuned and follow my profile to stay up to date – https://people.sap.com/dan.antonio.
Security-related UX Scenarios
These are some example scenarios we will use to discuss and show examples of how to implement a better user experience.
Scenario | Out-Of-The-Box UX | Better UX | Link to article/examples |
1. No Access – The user has no authorization to the application.
|
Generic 401 (Unauthorized) Error | Redirected to an “Access Denied” page, with instructions/link to how to request access. | Link (this article) |
2. Role-based actions – The user can only make changes to data when they have the correct authorization. | Generic 401 Error when trying to make changes to data | Dynamic UI which only shows authorized actions (buttons, links, etc.) | Link (Coming Soon) |
3. Role-based subset of data – The user can only access a certain subset of the data based on their authorizations.
|
N/A, custom filtering/coding required. | Automatic data filtering using attribute-based analytic privilege (no custom UI coding) | Link (Coming Soon) |
Pre-requisites
For the purposes of this article, I am using features and code examples which have been built on SAP HANA, Express Edition 2.0 SPS03. Any other versions of 2.0 should have essentially the same features, however code syntax may vary.
HANA Platform Security Model
HANA XSA uses roles as a way to implement and organize:
- Scope-based permissions
- Attribute-based permissions
Scope-bases permissions are typically used to control what the user can do (how they can interact with the data). For example, a “Viewer” can only display/analyze data, while an “Editor” can perform tasks against the data such as making changes or deleting content.
Attribute-based permissions are typically used to control which data the user can interact with (think of them as filters for the data). For example, a user with a “NorthAmerica” attribute, can only interact with data filtered by this attribute, while a “Europe” attribute would allow only for its data.
Scopes and attributes are part of roles, which can be combined to create aggregated role collections. Role collections are then assigned to user (or user groups).
In this blog we show how to perform these tasks using the HANA XSA security model using this logic flow.
It also possible to use the new Cloud Application Programming model to handle these scenarios. For more info on CAP, see: https://cap.cloud.sap/docs/
So, let’s take a look at some example implementations for the scenarios described above.
Scenario 1: No Access
This may seem like an unimportant use case, but often this is a critical use case for onboarding a new user, which if designed correctly should decrease the support load and ensure happier users.
This example will be shown using a web UI frontend (SAPUI5) and the API layer (NodeJS).
In order to apply role-based logic, we need to be able to get the specifics of the role (scopes and attributes) to the UI. The UI doesn’t have direct access to this information, but the API layer does, so in this example we will create a NodeJS service to return this information to the UI.
To keep this article simple, we will just give the essentials to begin with, making the assumption that you know how to create a basic MTA and modules. (If you don’t, then give this tutorial a try – https://developers.sap.com/group.hana-xsa-get-started.html)
A fully functional example of the source code can be reviewed/downloaded from here:
https://github.com/dantonio-sap/SecureUX (branch to use is “1-GetUserContext”)
Step 1 – Create an MTA and Modules (use these names if you want to be able to borrow code from the code samples)
Project/Module | Property | Value |
Multi-Target Application Project | ||
Project Name | SecureUX | |
Application ID | SecureUX | |
HANA Database Module | ||
Module Name | db | |
NodeJS Module | ||
Module Name | api | |
Enable XSJS support | true | |
SAPUI5 HTML5 Module | ||
Module Name | ui | |
Namespace | com.sap.secureux | |
View Type | XML | |
View Name | App |
Step 2 – Create UAA Service
This can be done using either the XSA Cockpit, or the XS CLI tool. In my sample code, I reference the service with the name “secureux-uaa”
xs-security.json
{
"xsappname": "SecureUX",
"scopes": [
{
"name": "$XSAPPNAME.Display",
"description": "display"
},
{
"name": "$XSAPPNAME.Create",
"description": "create"
},
{
"name": "$XSAPPNAME.Edit",
"description": "edit"
},
{
"name": "$XSAPPNAME.Delete",
"description": "delete"
}
],
"attributes": [
{
"name": "region",
"description": "region",
"valueType": "s"
}
],
"role-templates": [
{
"name": "Viewer",
"description": "View all records",
"scope-references": [
"$XSAPPNAME.Display"
],
"attribute-references": [
"region"
]
},
{
"name": "Editor",
"description": "Edit and Delete records",
"scope-references": [
"$XSAPPNAME.Create",
"$XSAPPNAME.Edit",
"$XSAPPNAME.Delete",
"$XSAPPNAME.Display"
],
"attribute-references": [
"region"
]
}
]
}
Step 3 – Bind services together in mta.yaml
The main changes here from the default are:
– setting up UAA as a resource and adding to requires for API and UI
– binding UI to API with requires/provides, and passing token from UI to API
mta.yaml
ID: SecureUX
_schema-version: '2.1'
version: 0.0.1
modules:
- name: ui
type: html5
path: ui
requires:
- name: core_api
group: destinations
properties:
forwardAuthToken: true
url: '~{url}'
name: core_api
- name: uaa
- name: api
type: nodejs
path: api
provides:
- name: core_api
properties:
url: '${default-url}'
requires:
- name: hdi_db
- name: uaa
- name: db
- name: db
type: hdb
path: db
requires:
- name: hdi_db
resources:
- name: uaa
type: com.sap.xs.uaa
parameters:
service-name: secureux-uaa
- name: hdi_db
properties:
hdi-container-name: '${service-name}'
type: com.sap.xs.hdi-container
Step 4 – Add NodeJS service for user context
- In the /api/lib folder, add a new file (userContext.xsjs) for the userContext service.
userContext.xsjs
try {
// Initialize hana connection/context
var oConn = $.hdb.getConnection();
var oSession = $.session;
$.response.status = $.net.http.OK;
$.response.contentType = "application/json";
$.response.setBody(JSON.stringify(oSession.securityContext));
oConn.close();
} catch(ex) {
// Return error
$.response.setBody("Failed to retrieve data");
$.response.status = $.net.http.INTERNAL_SERVER_ERROR;
}
*This code establishes a connection to HANA (to ensure the session is established) and returns the $.session.securityContext to the UI when called.
- Don’t forget to force authentication on by setting “anonymous = false” in /api/server.js
server.js (partial)
...
var options = {
anonymous : false, // remove to authenticate calls
redirectUrl : "/index.xsjs"
};
...
Step 5 – Enable authentication in the UI approuter and configure the routes for the API.
- In the approuter configuration (xs-app.json), you will define these routes
- /resources – no authentication required, so the UI code will run for all users
- /*.xsjs – Routes to api (NodeJS) service. Requires authentication, but not any scopes, so the APIs in this folder will run for authenticated users regardless of scopes
xs-app.json
{
"welcomeFile": "webapp/index.html",
"authenticationMethod": "route",
"routes": [
{
"source": "^/(.*)(.xsjs)",
"destination": "core_api",
"authenticationType": "xsuaa"
},
{
"source": "^/(.*)$",
"localDir": "resources"
}
]
}
Step 6 – Create UI to display user info when successfully authenticated
- Update the /ui/resources/webapp/controller/App.controller.js to request the user info from the API during the onInit() lifecycle method.
- See inline comments in the code for details on how the verification of the roles occurs.
App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/m/MessageBox"
], function (Controller, JSONModel, MessageBox) {
"use strict";
return Controller.extend("com.sap.secureux.ui.controller.App", {
onInit: function(){
this.getUserContext();
},
getUserContext: function(){
var oController = this;
var urlUserContext = "/userContext.xsjs";
var oModelUserContext = new JSONModel(urlUserContext);
// This code attached to the data request and parses the response. This example handles
// error scenarios (where the service is down, where the server returns 403/access denied,
// and finally it parses the scopes in the success authentication response to see if the
// user has a specific scope.
oModelUserContext
.attachRequestFailed(function(oEvent) {
var errorObject = oEvent.getParameters();
oController._parseResponse(errorObject);
})
.attachRequestCompleted(function(oEvent) {
var dataResponse = oEvent.getSource();
var errorObject = oEvent.getParameter("errorobject");
oController._parseResponse(errorObject, dataResponse);
});
// Set the resulting sessionContext to a user JSON model and bind to the view.
this.getView().setModel(oModelUserContext, "user");
},
_parseResponse: function(oError, oData){
if (oError){
// If Access Denied is returned by the server, redirect the user to the access denied
// page.
if (oError.statusCode === 403){
this.getOwnerComponent().getRouter().navTo("accessdenied");
}
else {
// If any generic error occurs, let the user know with a message.
jQuery.sap.delayedCall(1000, this, function(){
MessageBox.error("Unable to connect to server. Please check with IT helpdesk, or try again later. (" + oError.statusCode + ":" + oError.statusText + ")");
});
}
}
else{
// Verify Access - If the user has a "Display" scope then let them have basic access.
// If no "Display" scope is found then redirect them to the access denied page.
var oScopes = oData.getProperty("/scopes");
if (oScopes.filter(function(row) {
return (row.endsWith(".Display"));
}).length === 0) {
// No Access
this.getOwnerComponent().getRouter().navTo("accessdenied");
}
}
}
});
});
- Update the /ui/resources/webapp/view/App.view.xml to display the user info (assuming there will be a model called “user” which contains the securityContext from the API we created earlier)
App.view.xml
<mvc:View controllerName="com.sap.secureux.ui.controller.App" xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m">
<App id="idAppControl">
<pages>
<Page title="{i18n>title}">
<content>
<List binding="{user>/userInfo}" headerText="User Context">
<DisplayListItem label="logonName" value="{user>logonName}" />
<DisplayListItem label="givenName" value="{user>givenName}" />
<DisplayListItem label="familyName" value="{user>familyName}" />
<DisplayListItem label="email" value="{user>email}" />
</List>
<List items="{user>/scopes}" headerText="Scopes">
<DisplayListItem label="scope" value="{user>}" />
</List>
<List items="{user>/userAttributes/region}" headerText="Attributes (region)">
<DisplayListItem label="region" value="{user>}" />
</List>
</content>
</Page>
</pages>
</App>
</mvc:View>
Step 7 – Create roles, role collections and assign to your test user
This administration step can be done by using either the XSA Cockpit or the XS CLI tool.
For my testing, I created an XSA User for each of the role collections defined below, so I could verify the configuration was correct.
Roles
Role | Scope | Attribute |
SecureUX-Editor-NA | Editor | NA |
SecureUX-Editor-EU | Editor | EU |
SecureUX-Viewer-NA | Viewer | NA |
SecureUX-Viewer-EU | Viewer | EU |
Role Collections
Role Collection | Role |
SecureUX-Editor-NA | SecureUX-Editor-NA |
SecureUX-Editor-EU | SecureUX-Editor-EU |
SecureUX-Editor-Global |
SecureUX-Editor-NA SecureUX-Editor-EU |
SecureUX-Viewer-NA | SecureUX-Viewer-NA |
SecureUX-Viewer-EU | SecureUX-Viewer-EU |
SecureUX-Viewer-Global |
SecureUX-Viewer-NA SecureUX-Viewer-EU |
Step 8 – Deploy application and verify service is working
Now is a good time to deploy your code and do some testing of the positive (happy path) scenario. If your user has been granted access to the application, then you will see this (or a similar) resulting output page:
Step 9 – Create an Access Denied view, and set up routing
- Create a new view file (/ui/resources/webapp/view/AccessDenied.view.xml)
AccessDenied.view.xml
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" xmlns:semantic="sap.m.semantic" xmlns:footerbar="sap.ushell.ui.footerbar" xmlns:l="sap.ui.layout"
controllerName="com.sap.secureux.ui.controller.AccessDenied">
<MessagePage
icon="sap-icon://locked"
showHeader="true"
title="{i18n>AccessDenied.title}"
text="{i18n>AccessDenied.text}"
description="{i18n>AccessDenied.description}"/>
</mvc:View>
- Create a new controller file (/ui/resources/webapp/controller/AccessDenied.controller.js)
AccessDenied.controller.js (default)
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller ) {
"use strict";
return Controller.extend("com.sap.secureux.ui.controller.AccessDenied", {
onInit : function () {
}
});
}
);
- Update the resources file with the new text strings (/ui/resources/webapp/i18n/i18n.properties)
i18n.properties
title=SecureUX
appTitle=ui
appDescription=App Description
AccessDenied.title=Access Denied
AccessDenied.text=You do not have access to this application.
AccessDenied.description=Please check with your manager to request access.
- Update the application manifest file (manifest.json) with additional target and route for the access denied view.
manifest.json (partial)
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"async": true,
"viewPath": "com.sap.secureux.ui.view",
"controlAggregation": "pages",
"controlId": "idAppControl"
},
"routes": [
{
"name": "RouteView1",
"pattern": "RouteView1",
"target": [
"TargetView1"
]
},
{
"pattern": "accessdenied",
"name": "accessdenied",
"target": [
"AccessDenied"
]
}
],
"targets": {
"TargetView1": {
"viewType": "XML",
"transition": "slide",
"clearAggregation": true,
"viewName": "View1"
},
"AccessDenied": {
"viewType": "XML",
"clearAggregation": true,
"viewName": "AccessDenied"
}
}
}
Step 10 – Test “failed access” scenario
- Create a test user, and do not assign it to the role collection for this application. Try connecting to the application. After a successful login, you should be automatically redirected to the “Access Denied” view.
Conclusion
By spending a little time designing and building in logic like we’ve explored here, application designers and developers can help bridge the divide between users and security requirements.
Don’t forget – A fully functional example of the source code can be cloned or downloaded from here:
https://github.com/dantonio-sap/SecureUX (branch to use is “1-GetUserContext”)
So... My brain may not be working yet this morning. I have a few questions.
Thank you for the blog that has me questioning some things. It should make my development better and if it also give the reason why - that would be excellent.
Michelle
You could definitely tailor the message to type of authorization error. This was just a basic example. You could also externalize the "guidance" for the user so that it is stored in a HANA table and could be changed without a code change. You could also put all of this authorization logic into a single service that could be bound to all of your apps, so that you only have one instance of this code logic/authorization messages.
See the app.controlller.js for the logic on how it determines authorization.
Thank you! It seems like something I should be looking at sooner than later. It would be nice to have all the data (or most of it) given. So much easier to troubleshoot.
Hi Dan,
First of all this is an awesome blog to understand the HANA XSA security, I have created some applications and calling one application from another but getting login/callback issues.
Can you please suggest me any solution to the question on the community?
https://answers.sap.com/questions/13145444/hana-xsa-calling-one-application-from-another-appl.html
Hi https://people.sap.com/dan.antonio ,
This is an awesome blog! Thanks !
We did follow your blog but the userContext.xsjs is not retrieving the Security Context. We have raised a question . Its available in the link below.
$.Session.securityContext not returning scopes | SAP Community
Can you please suggest a solution for the same.