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: 
Raja
Product and Topic Expert
Product and Topic Expert
This blog series is mainly targeted for developers and administrators. If you are someone who has gone through the plethora of tutorials, documentation, and presentations on security topics in SAP BTP and still lacks the confidence to implement security for your application, you have come to the right place.

In this blog series, you will learn:

  • How to protect an app in SAP BTP, Cloud Foundry environment from unauthorized access

  • How to implement role-based features

  • How SAP Identity Provider and Single-sign-on works


 

For ease of reading, I have split this in multiple blogs, which are:

  1. Fundamentals of Security in BTP: Introduction

  2. Fundamentals of Security in BTP: What is OAuth?  (optional)

  3. Fundamentals of Security in BTP: Implement Authentication and Authorization in a Node.js App [current blog]

  4. Run Node.js Applications with Authentication Locally


 

Note: If you are new to SAP BTP and looking for a simple explanation of what it is and what problem it solves, see Explaining SAP Business Technology Platform (SAP BTP) to a Beginner

 

What are we going to learn in this blog?


So far, we have learnt about the core concepts of security. Now, let’s get our hands dirty by implementing the authentication and authorization.

  • We will first deploy an unsecured Node.js Hello World in BTP, Cloud Foundry

  • Then we will implement authentication in the application

  • Finally, we will add role based features for authorization



Note: Although, you can use any other IDE or command line interface to develop and deploy the Node.js app, we will recommend you use SAP Business Application Studio.

If you are new to SAP Business Application Studio, you may go through this tutorial to learn how to use it.

Let’s first deploy an unsecured Node.js App


I have saved a simple Node.js hello world application in GitHub. Follow below steps to deploy it to Cloud Foundry environment of BTP.

  1. Open Business Application Studio. Go to Terminal -> New Terminal and run below command.


                     
git init
git clone https://github.com/rajagupta20/unsecured-nodejs-app.git




  1. Click on Open Folder and select the application folder (unsecured-nodejs-app). Check below files.


start.js file - has basic hello world code


 

manifest.yml file - has runtime configurations


 

Execute the command cf push to deploy to your cloud foundry space and try to access the app.

Result


You just deployed a Hello World Node.js application to BTP. You should be able to access the application without any authentication required.

 

Let’s implement authentication to the Node.js App


Now, let's modify this Node.js application, so that only authenticated users will be able to access it.

Let’s quickly recap what we learnt in previous blogs.




  • business user wants to access your application.

  • The single point of entry is the application router.

  • The application router sends the request to XSUAA.

  • The XSUAA further forwards the request to Identity Provider, which takes care of authentication.


 

To implement authentication, all we need to do is:

  1. Implement App Router

  2. Create an instance of XSUAA service

  3. Bind the application and App Router with XSUAA instance

  4. Modify the application to make sure it only accepts request contains a JWT token


 

To make it simple, I have saved completed project in GitHub. Let's clone that by running below commands:

               
  git init

git clone https://github.com/rajagupta20/secured-nodejs-app-demo1.git

 

Now, let’s check authentication specific implementation.

 

1.    Implement App Router


Remember - technically, Application Router is a Node.js App, available in public in NPM.

To add an App Router in our application, all we need to do is:

  1. Create a folder (say approuter)

  2. Create a package.json file inside the folder

  3. In package.json file - add the dependency for Approuter and configure its start point

  4. Create another file called xs-app.json inside approuter folder to configure the App Router


Check the cloned project and look into these 2 files.

package.json


 

xs-app.json




xs-app.json (Routing Configuration File)


App Router’s configuration is defined in the file called xs-app.json. In this example, we are keeping it simple by just adding a route. The route says - If the response URL pattern matches the given regex expression, forward it to a destination (here myapp).

The destination (myapp) and App Router (approuter1) is specified in manifest.yml file as shown below.


 

Note: In later blogs, we will look into xs-app.json file in detail.

2.    Create an instance of XSUAA service


Before creating an instance of XSUAA, you need an important file called xs-security.json.

xs-security.json (Application Security Descriptor)


Check the file called xs-security.json in the cloned project.


 

xs-security.json is the file which defines the details of the authentication methods and authorization types to use for access to your application.

The xs-security.json file uses JSON notation to define the security options for an application. The information in this file is used at application-deployment time, for example, to create required roles for your application.

 

In this example, we are making it extremely simple, by just specifying xsappname and tenant-mode.

  • xsappname property specifies the name of the application that the security description applies to.

  • tenant-mode can be “dedicated” for single-tenant application and “shared” for multitenant application


 

Create instance of XSUAA using xs-security.json file


There are different ways to create an instance of XSUAA. Major options are:

  1. Go to BTP Cockpit -> Subaccount -> Space -> Instance -> Create as shown below



 

  1. Use command line tool. Execute below command


     
cf create-service xsuaa application <service_instance_name> -c xs-security.json

 

  1. In case of Multitarget Applications (MTA), use configurations specified in yml file, which automatically creates XSUAA instances and bind it with applications. This is the most preferred way, and we will look into it later.


 

 

To make sure we understand what’s happening behind the scenes, let’s use command approach. Executing below command to create the XSUAA instance.      
cf create-service xsuaa application nodeuaa -c xs-security.json

 

Here nodeuaa is the XSUAA instance name.

3. Bind the XSUAA instance with application and App Router


We can specify the binding of XSUAA instance with application and app router in manifest.yml file as shown below.


 

You can check the XSUAA instance and binding status in BTP cockpit as shown below.


 

4.    Modify the application to make sure it only accepts request contains a JWT token


Finally, we need to make sure that application entertain only authenticated request containing a valid JWT token. In Node.js we can do that by using passport module.


That’s it. We have implemented the authentication in our Node.js application.

 

Execute cf push command to deploy the application. It will deploy both app router and application. You can check them in the BTP cockpit.

Test the Application


Try to access the application (“myapp-secured-demo1”) directly. It would give “Unauthorized” error.


 

Now, try to access the application via app router, it will first redirect to Identity Provider and once authenticated (either using username/password or certificate-based login), it redirects to application. Below image shows the call flow if SAP ID Service is used as Identity Provider.


 

How Authorization works in SAP BTP, Cloud Foundry


Let’s move to authorization. Before doing the hands-on, let’s first understand few important points about XSUAA and user-role assignment.

XSUAA Design Time and Runtime Artifacts


A quick recap:

  • xs-security.json file is used to create an instance of XSUAA

  • When XSUAA instance is being created, it generates few runtime artifacts


 

A typical xs-security.json file has 3 design time artifacts:

  1. Scope

  2. Attribute

  3. Role-Collection



Here is a sample xs-security.json file.
{
"xsappname": "myapp2",
"tenant-mode": "dedicated",
"scopes": [
{
"name": "$XSAPPNAME.Display",
"description": "Display Users"
},
{
"name": "$XSAPPNAME.Update",
"description": "Update Users"
}
],
"role-templates": [
{
"name": "Viewer",
"description": "View Users",
"scope-references": [
"$XSAPPNAME.Display"
]
},
{
"name": "Manager",
"description": "Maintain Users",
"scope-references": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Update"
]
}
]
}

 

Scope


Scopes are functional authorizations that are assigned to users by means of security roles.

These scopes can be used for functional authorization checks. For example, in the application, we may check the scope using checkScope() function as shown below.
app.get('/users', function (req, res) {
var isAuthorized =
req.authInfo.checkScope('$XSAPPNAME.Display');
if (isAuthorized) {
res.status(200).json(users);
} else {
res.status(403).send('Forbidden');
}
});

 

Attribute


We can use attributes to perform more granular level checks. For example, a manager may have authorization to update employee data but only for a specific country.

In xs-security.json file we create the attribute (say country) and value of the country is assigned at runtime.

Role Templates


A role template combines the scopes and attributes. It is the description of roles (for example, “manager” or “employee”) to apply to a user.

 

When we create an XSUAA instance, it generates a runtime artifact called Role.



Role


Role is created based on Role Template at runtime. You can check the generated artefacts in the BTP Cockpit as shown below.


 

Assignment of Roles and Role Collection to Users


In BTP, there is something called Role Collection which helps to bind the Roles to Users.

Role Collection


Role Collections contain one or more roles. Further role collections are assigned to users by administrator.


The above image shows:

  • the design time (Role Template, Attribute & Scope) and run time artifacts (Role) of XSUAA. Role

  • Roles are combined in Role Collection

  • Role Collections is assigned to users

  • XSUAA instance is also bound to the application


Below image shows the tasks/responsibilities of developer and administrator in this context.



Implement authorization in the Node.js application


Now, let’s implement authorization to the same Node.js app and add some role-based features.

We will:

  • Add 2 roles in the application – Manager and Viewer

  • Implement that –

    • A user having Viewer role can only view the records

    • But a user having Manager role can insert/delete the records.




 

To make it simple, I have saved completed project in GitHub. Let’s clone that by running below commands:
git init

git clone https://github.com/rajagupta20/secured-nodejs-app-demo2.git

 

Now, let’s check authorization specific implementation.

1. Add Scope and Role Collection in xs-security.json file


We have added:

  • 2 Scopes - Display and Update

  • 2 Role Templates – Viewer and Manager


in xs-security.json file
{
"xsappname": "myapp2",
"tenant-mode": "dedicated",
"scopes": [
{
"name": "$XSAPPNAME.Display",
"description": "Display Users"
},
{
"name": "$XSAPPNAME.Update",
"description": "Update Users"
}
],
"role-templates": [
{
"name": "Viewer",
"description": "View Users",
"scope-references": [
"$XSAPPNAME.Display"
]
},
{
"name": "Manager",
"description": "Maintain Users",
"scope-references": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Update"
]
}
]
}

 

2. Implement role based features using Scope


In start.js file, notice the use of checkScope() function to implement role based feature. User can get the data only if he has “Display” scope. Similarly, user can edit the data only if he has “Update” scope.

 
const express = require('express');
const passport = require('passport');
const bodyParser = require('body-parser');
const xsenv = require('@sap/xsenv');
const JWTStrategy = require('@sap/xssec').JWTStrategy;

const users = require('./users.json');
const app = express();

const services = xsenv.getServices(
{ uaa: 'nodeuaa2' }
);
passport.use(new JWTStrategy(services.uaa));

app.use(bodyParser.json());
app.use(passport.initialize());
app.use(passport.authenticate('JWT', { session: false }));

app.get('/users', function (req, res) {
var isAuthorized =
req.authInfo.checkScope('$XSAPPNAME.Display');
if (isAuthorized) {
res.status(200).json(users);
} else {
res.status(403).send('Forbidden');
}
});


app.post('/users', function (req, res) {
const isAuthorized = req.authInfo.checkScope('$XSAPPNAME.Update');
if (!isAuthorized) {
res.status(403).json('Forbidden');
return;
}

var newUser = req.body;
newUser.id = users.length;
users.push(newUser);

res.status(201).json(newUser);
});

const port = process.env.PORT || 4000;
app.listen(port, function () {
console.log('myapp listening on port ' + port);
});

 

 

Note that we have a static resource approuter -> resources -> index.html. There is no authorization required (no roles required) to access this file.

3. Create XSUAA instance and deploy the app


As we learned before, run below command to create XSUAA instance.

cf create-service xsuaa application nodeuaa2 -c xs-security.json

Note, that XSUAA instance name is nodeuaa2, the same is bound to application in manifest.yml file.
---
applications:
- name: myapp-secured-demo2
routes:
- route: node-12345671-3.cfapps.eu10.hana.ondemand.com
path: myapp
memory: 128M
buildpack: nodejs_buildpack
services:
- nodeuaa2

- name: approuter2
routes:
- route: approuter1-12345671-3.cfapps.eu10.hana.ondemand.com
path: approuter
memory: 128M
env:
destinations: >
[
{
"name":"myapp",
"url":"https://node-12345671-3.cfapps.eu10.hana.ondemand.com",
"forwardAuthToken": true
}
]
services:
- nodeuaa2

 

Next, execute cf push to deploy the app.

 

4. Create Role Collection and assign to user


Open BTP Cockpit and go to Subaccount  -->  Role Collections --> Create and create a role collection called Manager.


 

Select the role collection and add Manager and Viewer roles from your application. Also add your user (or any user you want to give access) to it.


 

Similarly, you may create another Role Collection called Viewer and only assign Viewer role to it.

Test the Application


Now, try to access the application via App Router. You can get the App Router URL from cockpit as shown below.




Scenario 1 – User does not have Manager or Viewer role collection assigned


You can access the static resource. But if you click on “Show users”, you will get Forbidden error message.




Scenario 2 – User has only Viewer role collection assigned


You can access the static resource as well as see the user data.


 

However, if you try to add a new user, you get forbidden error message.




Scenario 3 – User has Manager role collection assigned


You can access the static resource as well as see the user data. You can also add a new user.


 

I hope now you got overall idea on how authentication and authorization works.
If you have any queries, let me know in comment or get in touch with me at LinkedIn!
23 Comments
sukai_tian
Explorer
0 Kudos

Nice blog!

Can you help on below two questions?

  1. Is it possible to test myapp application or approuter application in BAS IDE locally before deploy it to remote CF?
  2. It raises error "The redirect_uri has an invalid domain" after push code to CF and display Approuter URL, any idea how to resolve it?

 

Raja
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi sukai.tian ,

 

  1. Yes, you can run the application locally in BAS. However, it requires few manual steps. I will create a separate blog for this and publish soon.

  2. Did you just cloned this app, created XSUAA instance named nodeuaa and deployed the app to cloud foundry using cf push? Or made any changes? Could you please provide the log.


 

Regards,

Raja
BitanC
Advisor
Advisor
0 Kudos
Hello Raja,

Thanks for your blog series.

Wondering if this blogpost "Fundamentals of Security in BTP: OAuth Concept (optional)" is live , there is no URL and search did not hit a successful result.

Looking forward to it.

Best Regards,

Bitan
Raja
Product and Topic Expert
Product and Topic Expert

Hi bitanc 

Thanks.

The blog Fundamentals of Security in BTP: What is OAuth? is live now. 

 

Regards,

Raja

Raja
Product and Topic Expert
Product and Topic Expert
Hi sukai.tian ,

I have published the blog on how to run this application locally - Run Node.js Applications with Authentication Locally
sukai_tian
Explorer
0 Kudos

Hi rajaprasad.gupta 

Thanks for the blog!

For question 2, I tested again, and faced below error when display approuter application even I cloned your code and created XSUUA instance.

Could you please help?

Application log of Approuter:

{"written_at":"2022-08-08T11:35:58.488Z","written_ts":1659958558488000000,"csn_component":"-","correlation_id":"63aa4279-0ca5-4997-73bc-7cfb41771987","type":"log","logger":"nodejs-logger","layer":"/Auth/OAuth2","level":"info","container_id":"10.32.1.5","component_type":"application","component_id":"cd4e624a-d20e-44c9-aa95-9929b4a06a01","component_name":"approuter1","component_instance":-1,"source_instance":-1,"organization_id":"","organization_name":"trial","space_id":"07e71db2-7e4f-401f-aee7-7beda347a887","space_name":"dev","request_id":"63aa4279-0ca5-4997-73bc-7cfb41771987","msg":"sending page with client-side redirect to https://trial.authentication.ap21.hana.ondemand.com/oauth/authorize?response_type=code&client_id=sb-myapp1!t7232&redirect_uri=https%3A%2F%2Fsk_approuter1-12345671-2.cfapps.ap21.hana.ondemand.com%2Flogin%2Fcallback"}

{"written_at":"2022-08-08T11:35:58.487Z","written_ts":1659958558487000000,"csn_component":"-","correlation_id":"63aa4279-0ca5-4997-73bc-7cfb41771987","type":"log","logger":"nodejs-logger","layer":"/Auth/OAuth2","level":"info","container_id":"10.32.1.5","component_type":"application","component_id":"cd4e624a-d20e-44c9-aa95-9929b4a06a01","component_name":"approuter1","component_instance":-1,"source_instance":-1,"organization_id":"","organization_name":"","space_id":"","space_name":"dev","request_id":"63aa4279-0ca5-4997-73bc-7cfb41771987","msg":"query does not exist for request url /"}

sukai_tian
Explorer
0 Kudos
Thanks Raja. The issue is resoved. The approuter url of manifest.yml can't contain any underscore in URL address~

After change underscore to dash, it works fine

Wrong: sk20220809_approuter.cfapps.XX.hana.ondemand.com
Correct: sk20220809-approuter.cfapps.XX.hana.ondemand.com
julian2000
Member
0 Kudos
Hello Sukai,

 

facing exactly the same error message as you described above. Only difference is that we do not have any underscores in URL address of approuter.

 

Did you made any other changes to solve the error?

 

Best regards,

Julian
sukai_tian
Explorer
0 Kudos

Hi Julian,

To resolve my previous error, I only adjusted the URL. There is no other change.

You can create another project as this blog steps and check it again. Hope it helps.

Best regards,

Sukai

 

helge_volkmar
Advisor
Advisor
0 Kudos
Hi Raja,

thanks for this great blog. Under "Create instance of XSUAA" the case of Multitarget Applications is mentioned, and it says that it will be covered later.

Is there some more information on this option available?

Thanks

Helge
Raja
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi helge.volkmar ,

For complete reference and examples of XSUAA, you may refer to this help document - https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/902ae800c1d04c7388e407b7815e5cc8.html

 

Regards,

Raja Gupta
Mohit_Agarwal
Associate
Associate
0 Kudos

Hi Raja,

 

I see that you've provided the routes to both the applications in manifest.yml manually.

I was wondering how do we implement the destinations in manifest.yml if we do not specify the route url and leave it upto BTP to decide the url?

 

Regards,

Mohit

rds88
Explorer
Regarding the "The redirect_uri has an invalid domain" error, for me it worked after modifying the "xs-security.json" as proposed in https://me.sap.com/notes/2864831/E, adding the URI to the app router instance, then updating the XSUAA service.

You can update the service by invoking "cf update-service nodeuaa -c xs-security.json".
{
"xsappname": "myapp",
"tenant-mode": "dedicated",
"oauth2-configuration": {
"redirect-uris": [
"https://<path-to-approuter>/**"
]
}
}

Also note https://blogs.sap.com/2019/03/14/announcement-no-implicit-wildcards-in-redirect-uris-supported-anymo....
andybessm001
Discoverer
0 Kudos
Hi Raja,

 

First off, thanks a lot for the blog, it's very helpful and informative! I've got one question: did you try to set up authentication for node.js which is not running in BTP environment? We have a Converged Cloud box which runs js script in node and need to have an automated authentication process for it, do you have such an experience?

Thanks in advance.

Best regards,

Andy
Raja
Product and Topic Expert
Product and Topic Expert
0 Kudos
andybessm001 ,

Thanks for the feedback.

I have not done any hands-on for converged cloud but it would be similar. Except the XSUAA, remaining concepts around authentication like passport module, JWT token etc. would remain same.

Let me know if you have any specific question on this.

 

Regards,

Raja Gupta
xiongjun_li
Explorer
0 Kudos
Hi Raja,

 

Nice blog.

Currently I'm experiencing a problem that I tried to call api with xsuaa authentication in Postman/nodejs, but I always got the response with redirect html page like below, even i added access_token in the header of request.

Do you have any suggestions or comments on how should I figure out this issue?

<html><head><link rel="shortcut icon" href="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" /><script>document.cookie="fragmentAfterLogin="+encodeURIComponent(location.hash)+";path=/";document.cookie="locationAfterLogin="+encodeURIComponent(location.href.split('#')[0].split(location.host)[1])+";path=/";document.cookie="signature=5NZKHzlLoeLqa94f0hU0F9%2BhP6M%3D;path=/";location="https://05fb0c02trial.authentication.us10.hana.ondemand.com/oauth/authorize?response_type=code&client_id=xxxx&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Flogin%2Fcallback"</script></head></html>

 

Thanks in advance.

Best regards,

Xiongjun
fernandodemaio
Discoverer
0 Kudos
Hello, good afternoon! The tutorial is excellent, I applied it in my project and it worked perfectly. But I have a problem, I can't correctly configure the destination that exposes the service, when they want to be consumed from a ui5 application it returns unauhtorized.
Someone had the same problem?

Thank you very much, greetings!
Raja
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi fernandodemaio, Can you please share your manifest.yml file.

 

Regards,

Raja Gupta
fernandodemaio
Discoverer
Hi Raja, thank you very much for your reply, it may fix the problem. I only had to move a line of code.

Regards!
Raja
Product and Topic Expert
Product and Topic Expert
0 Kudos
Great fernandodemaio.

Let me know if you have any other query.

 

Regards,

Raja Gupta
former_member760028
Discoverer
Hi Raja

You are Raja (King) on Security

Well written and very good structure.

Keep it up

regards

Thomas
Raja
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi tomhanatech,

Thank you for the lovely feedback 😀😀

 

Regards,

Raja Gupta
mcaisucar
Explorer
0 Kudos

hi @Raja 

the code for unsecured gave me compile time error.

**ERROR** Unable to install node: no match found for 14.x.x in [18.18.2 18.19.0 20.10.0 20.11.0]

i changed the 

"node": "20.11.0"

the compilation went ahead but gave error 

   Installing node modules (package.json + package-lock.json)

BuildpackCompileFailed - App staging failed in the buildpack compile phase

FAILED

 

How can i troubleshoot it . Pl help