Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Dan_Antonio
Product and Topic Expert
Product and Topic Expert
2,899
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 - 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”)
5 Comments