Skip to Content
Technical Articles
Author's profile photo Raja Prasad Gupta

Fundamentals of Security in BTP: Implement Authentication and Authorization in a Node.js App

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

 

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 question, let me know in the comment.

Assigned Tags

      7 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Sukai Tian
      Sukai Tian

      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?

       

      Author's profile photo Raja Prasad Gupta
      Raja Prasad Gupta
      Blog Post Author

      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

      Author's profile photo Sukai Tian
      Sukai Tian

      Hi Raja Prasad 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 /"}

      Author's profile photo Sukai Tian
      Sukai Tian

      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

      Author's profile photo Raja Prasad Gupta
      Raja Prasad Gupta
      Blog Post Author

      Hi Sukai Tian ,

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

      Author's profile photo Bitan Chakraborty
      Bitan Chakraborty

      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

      Author's profile photo Raja Prasad Gupta
      Raja Prasad Gupta
      Blog Post Author

      Hi Bitan Chakraborty 

      Thanks.

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

       

      Regards,

      Raja