Skip to Content
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”)

3 Comments
You must be Logged on to comment or reply to a post.
  • So…  My brain may not be working yet this morning.   I have a few questions.

    1. It would take a lot of work to change the access denied message.  There are a lot of apps out there.  Now in the future when I design an app I could certainly capture it.  So I am bookmarking for my next project.
    2. I looks like you are still giving a generic message.  I’m wondering if we could change it to a specific message so that when we have to trouble shoot the program, we can find the issue better.  What was denied?  What did the program look at to determine that message.  That type of thing.  Now this is a simple example, so I’m thinking to put together a more dynamic message would be something we could do?

    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.