Skip to Content
Technical Articles

How to call protected app | from external app || as external user ||| with scope

or:

Calling
oauth & scope protected app
from other app
using client credentials
and as external user

or:

How to assign scope
to foreign app
and external user

The problem I’d like to address with this tutorial is the following:
Scenario: In SAP Cloud Platform, we have app 1 and app 2:

App1:
Provides API
protected with OAuth2
requires scope
< – – – – > App 2:
The client app
that calls app 1

The problem:
To call the API, the cliet has to do the OAuth flow programmatically, that’s no issue.
BUT:
The JWT token doesn’t contain the scope which is required by app 1
How to assign a role to an app???

Quicklinks:
Quick Guide
Sample Code

Prerequisites

  • We choose Node.js to write the little apps
    So you need to have Node.js installed on your machine
    See here for little help, no need to be very familiar with node.js development
  • Access to SAP Cloud Platform, Trial account (see here)
  • Some experience with app development, app deployment
  • Command Line Client for Cloud Foundry (see here) is not a must, but useful

Overview

1 Create API Provider App
2 Call API with human user
3 Create Client App and call API

Appendix: All Sample Project Files

Preparation: Create Project Structure

Create base project folder for our 2 apps:
C:\tmp_app2app
Inside, we create 2 root folders for our apps, and in addition, a third folder for our optional human test:
apiproviderapp
clientapp
humanuser

See screenshot:

Step 1: Create API Provider App

First we create our app 1,  the app which provides protected API, we call it providerapp
To protect our app, we use the XSUAA (Extended User Authentication and Authorization) service in SAP Cloud Platform

1.1. Create XSUAA instance

To create an instance of xsuaa, we define first the configuration parameters, which are required during instance creation.
The parameters can be copy&pasted directly in the cloud cockpit, but it is better to store it in a descriptor file

Jump into C:\tmp_app2app\apiproviderapp
Create a file called xs-security.json

Note:
The name is not relevant, you can use any name of your choice
But xs-security.json is the usual name

Copy the following content:

{
  "xsappname" : "xsappforproviderapp",
  "tenant-mode" : "dedicated",
  "scopes": [{
      "name": "$XSAPPNAME.scopeforproviderapp",
      "granted-apps" : [ "$XSAPPNAME(application,xsappforhumanuser)"],
      "grant-as-authority-to-apps" : [ "$XSAPPNAME(application, xsappforclientapp)"]
  }],
  "role-templates": [ { 
      "name"                : "TheProviderRoleTemplate", 
      "default-role-name"   : "TheRoleForProviderApp",
      "scope-references"    : ["$XSAPPNAME.scopeforproviderapp"]
  }]
}

With this configuration, we define a scope and new role in SAP Cloud Platform
That role can be assigned to users
Later, we will bind our app to this instance of xsuaa, and with the help of xsuaa, we’ll ensure that the API can only be invoked if the caller has that scope

Note:
The role-templates section is only required for human users.
If you’re planning to skip the optional chapter 2, you can remove that part from the json

Note:
The value of xsappname property must be unique in the identityzone

Note:
Prefixing the scope name with the variable $XSAPPNAME makes the scope name unique and accessible
The variable will be resolved by the platform, we will see it below

Let’s continue
Based on the xs-security.json file, we can create the instance of xsuaa service, with name xsuaaforprovider

In command prompt, folder apiproviderapp, run the following command

cf cs xsuaa application xsuaaforprovider -c xs-security.json

The syntax:
create service instance of service <xsuaa> and service plan <application>. Then the desired <name> of the instance and the <configuration> params as file

You can also use the cloud cockpit and use the xs-security.json file in the creation wizard

After the service instance is created, we can use it in our app.

1.2. Create app

Our app is a silly little app which exposes a silly REST endpoint
Nevertheless, the app absolutely wants to protect the endpoint with OAuth2
That’s tedious enough….but:
Furthermore, the app wants that whoever is silly enough to call the endpoint, he must have a role (scope) assigned

Installation

In folder C:\tmp_app2app\apiproviderapp create a file called package.json

With following content:

{
  "main": "server.js",
  "dependencies": {
    "@sap/xsenv": "latest",
    "@sap/xssec": "latest",
    "express": "^4.16.3",
    "passport": "^0.4.1"
  }
}

The dependencies are required to start a server and to add security
To install the dependencies, open command prompt, jump into folder apiproviderapp and execute the command npm install
See here:

Implementation

In the folder C:\tmp_app2app\apiproviderapp create a file called server.js
Copy the full content which can be found in the appendix section

Explanation:
Our little app exposes a REST endpoint which just returns a message.

app.get('/getData', function(req, res){   

The authorization header, which contains a JWT token, is analyzed to view if the required scope is present.

const MY_SCOPE = xsuaaCredentials.xsappname + '.scopeforproviderapp'// scope name copied from xs-security.json
if(req.authInfo.checkScope(MY_SCOPE)){
   res.send('The endpoint was properly ca

The check for valid JWT token is done by passport module

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

In addition, just for our convenience, we add a middleware which is invoked prior to all other code.

app.use(jwtLogger)

This middleware just decodes and prints the relevant content of the JWT token

function jwtLogger(req, res, next) {
   const jwtToken = readJwt(req)
   console.log('===> JWT: scopes: ' + jwtToken.scope);

1.3. Deploy

In our app folder C:\tmp_app2app\apiproviderapp we create a file with name manifest.yml
The content:

---
applications:
- name: providerapp
  memory: 128M
  buildpacks:
    - nodejs_buildpack
  services:
    - xsuaaforprovider

Explanation:
We define a name for our app in the cloud
We declare a dependency to the instance of xsuaa service which we created earlier

To deploy, we run the following command in the same folder apiproviderapp
cf push
(requires the Command Line Client, see prerequisites)

Step: 2 Call API with human user

After deployment, we can invoke the endpoint of our app in the browser.
https://providerapp.cfapps.eu10.hana.ondemand.com/getData
Result: 401 Unauthorized
We check the logs with cf logs providerapp –recent
Result: No relevant info in the log
Our endpoint implementation is not even reached.
Reason: we haven’t sent a JWT token
Consequence: We need to do the OAuth flow

How to call an OAuth protected service that requires OAuth flow?
I’ve described it in detail in this blog
So here I’m only giving short description

How do we get a JWT token?
We have to use xsuaa

2.1. Create xsuaa

So we create a new instance of xsuaa, with name xsuaaforhuman
Why create a new instance of xsuaa, why not use the one we created above?
That would be possible, but we’re assuming a scenario where the user is not the same person who created the API provider app

In folder C:\tmp_app2app\humanuser we create a file xs-security.json with following content

{
  "xsappname" : "xsappforhumanuser",
  "tenant-mode" : "dedicated",
  "foreign-scope-references": [
    "$XSAPPNAME(application,xsappforproviderapp).scopeforproviderapp"
  ]
}

Explanation:
The syntax is:
<service plan>, <xsappnameofprovidingxssecurityfile>.<scopename>

Note:
Make sure not to use e.g. the instancename, or applicationname, or the rolename

Note:
Alternatively, the following statements are valid as well:

"$XSAPPNAME(application,b2d58db5-635f-44a2-97d6-8b23be70fd5c,xsappforproviderapp).scopeforproviderapp"

Above one contains the subaccount id
Below one is using the resolved xsappname instead of the variable

"xsappforproviderapp!t45671.scopeforproviderapp"

Now, to create the instance of XSUAA, open command prompt, step into folder
C:\tmp_app2app\humanuser and run

cf cs xsuaa application xsuaaforhuman -c xs-security.json

Or use the cockpit to create an instance with name xsuaaforhuman

Since we’re a human, we cannot bind ourself to the service.
As such, we need to create a service key.
That can be done in the cockpit, in the details screen of the service instance
Or on command line:
cf csk xsuaaforhuman serviceKeyForHuman
Syntax:
Create service key for instance and give the desired name

Then we have to view the content of the service key.
In the cockpit it is visible, on command line run:

cf service-key xsuaaforhuman serviceKeyForHuman

Now, from the service key, take a note of the following properties:

“clientid”: “sb-xsappforhumanuser!t12345”,
“clientsecret”: “CLLaQTTjfG1i9PGrBrRLaIigpjo=”,
“url”: “https://123abc456trial.authentication.eu10.hana.ondemand.com”,

2.2. Call API with REST client

Now use a REST client tool which supports OAuth to fetch a JWT token and call our REST endpoint
Support for OAuth means that the tool does an additional request (to fetch the token) before it executes the actual desired request to the endpoint
In the details dialog for OAuth2, enter the following details:

Authorization OAuth2
Grant Type Password
URL append oauth/token to the url property
e.g.
…authentication…ondemand.com/oauth/token
User / Password your own Cloud Platform user/password
ClientID / secret values from properties from service key

The screenshot shows the configuration in postman:

Press “request”, then “use token”, then “send”

Result: 403 Forbidden
AGAIN!
But this time, in the logs, we can see that the JWT has been sent, but it doesn’t contain the required scope
Reason:
As a foreign user we want access to foreign scope, as declared in our xsuaa descriptor
But wanting it is not enough.
The providing app has to allow it
That’s fair enough…

2.3. Grant the scope

As a provider app, I can allow a foreign user to use my scope
What is a “foreign user”?
That’s somebody who uses a different instance of XSUAA
To allow, I have to GRANT my scope to the foreign user
The foreign user is represented by the instance of xsuaa which he is using
And the instance of xsuaa is represented by the property xsappname
As such, the provider app grants the scope to the foreign xsappname
xsappname is called “app”, see below.

As such, the statement is:

"granted-apps" : [ "$XSAPPNAME(application,xsappforhumanuser)"]

Syntax:
<service-plan>,<xs-app-name>

As a provider app, I have to add this statement to my xsuaa descriptor when creating the instance of xsuaa

The excerpt of xs-security.json

"xsappname" : "xsappforproviderapp",
"scopes": [{
   "name": "$XSAPPNAME.scopeforproviderapp",
   "granted-apps" : [ "$XSAPPNAME(application,xsappforhumanuser)"]
}],
...

See appendix for full file content

In our tutorial, we’ve already created the xsuaa instance (providerapp), so now, we have to “update” the instance, to make the changed parameters effective
Updating a service instance is only possible in command prompt:

cf update-service xsuaaforprovider -c xs-security.json

If you don’t use the command line, you have to delete the xsuaa instance in the cockpit, then re-create it with new parameters

After updating the xsuaa of the provider app, we have a nice relation between the 2 xsuaas:
The provider does “grant”, the humanuser does “reference” it

We can try the request with postman again:
fetch new token, use it, then send the request
But we still get forbidden and in the log we still don’t see the scope in our JWT token

One more step is required:
The human user needs to have the corresponding role assigned
Remember: the api provider app not only defined a scope (with grant), but also defined a role-template, based on the scope.
Like that, the role can be assigned to human users

2.4. Assign Role to user

In Cloud Cockpit, go to subaccount
On the left menu: Security->Role Collections
Create a Role Collection with a name of your choice
Click the new Role Collection, then “Add”
In the dialog, choose the application identifier, e.g.
xsappforproviderapp!t12345

Note:
Remember?
This is the value we gave for property xsappname in the xs-security.json file
The platform has generated the full name.
That’s why we couldn’t hardcode it in the application code of our provider app

After adding the role to the role collection, we have to assign the role collection to our user
In subaccount, Security->Trust Configuration click on the active entry (“sap.default”)
Enter the e-mail address then click Show Assignments, then Assign Role Collection
Choose your new Role Collection, close the dialog
Now your user owns that role (the scope defined by the xsuaa bound to providerapp)

Call endpoint

In postman, request new token, use it, send the request to the endpoint of providerapp
Finally, the request is successful
We see in the log that the scope was contained in the JWT token

Recap

Scenario:
There’s a provider app which requires a scope
And there’s a human foreigner which wants to call that app
The human user is a foreigner because he has his own xsuaa
To enable this scenario:
1. the provider app has to grant the scope explicitly to the foreign xsuaa
2. the foreign xsuaa has to explicitly reference the granted scope
3. the foreign human user needs the (greanted) role assigned to him

Note:
Instead of postman, we could have used an app router. See here

Diagram

Step 3: Create Client App and call API

Now we’re coming to the actual purpose of this tutorial:
we want to call the endpoint of the provider app programmatically, from a second app
This app as well needs its own instance of xsuaa
In the previous, optional chapter, we’ve learned that the 2 involved xsuaa instances have to reference each other
However, the mechanism is different for client apps than for human users.

Let’s see

3.1. Create xsuaa for client app

In foder C:\tmp_app2app\clientapp we create a new xs-security.json file with content:

{
  "xsappname" : "xsappforclientapp",
  "tenant-mode" : "dedicated",
  "authorities":["$ACCEPT_GRANTED_AUTHORITIES"]
}

The relevant line here is the “authorities” property.
This means, with other words, that the client app wants to use the authorities which are granted by providing app

Note:
This statement can be refined to accept only certain scope:

"authorities":["$XSAPPNAME(application,xsappforproviderapp).scopeforproviderapp"]

Or this variant works as well, easier to read, with resolved variable (but don’t use it)

"authorities":["xsappforproviderapp!t45671.scopeforproviderapp"]

Note:
Foreign-scope-reference doesn’t work
That’s only used in user-centric scenarios

To create this service instance, open command prompt and make sure to step into in the clientapp folder
Then execute the following command
cf cs xsuaa application xsuaaforclient -c xs-security.json

Note:
We don’t need to create service key, because we’re going to bind the client app to this xsuaa instance

3.2. Update the providing xsuaa

Now we need to change the xsuaa descriptor of the providing app.
In order to allow a scope to a foreign app (not user), the following statement is used:

"grant-as-authority-to-apps" : [ "$XSAPPNAME(application, xsappforclientapp)"]

As we can see, here the scope is granted as “authority”

Syntax:
<service plan>, <xsappnameofusingapp>

Note:
Make sure to enter the correct xsappname of the xsuaa created above for the client

The relevant excerpt of file C:\tmp_app2app\apiproviderapp\xs-security.json:

"xsappname" : "xsappforproviderapp",
"scopes": [{
  "name": "$XSAPPNAME.scopeforproviderapp",
  "grant-as-authority-to-apps" : [ 
    "$XSAPPNAME(application, xsappforclientapp)"
...

See appendix for full file content

After changing the xs-security.json file, don’t forget to call the “update-service” command
Open command prompt in folder C:\tmp_app2app\apiproviderapp and run
cf update-service xsuaaforprovider -c xs-security.json

After updating the xsuaa of the provider app, we have a nice relation between the 2 xsuaas:
The provider does “grant”, the client does “accept” it:

3.3. Create the client app

package.json

In folder C:\tmp_app2app\clientapp create a package.json file and copy the content from the appendix section

server.js

In folder C:\tmp_app2app\clientapp create a server.js file and copy the content from the appendix section

In the implementation, we do the following:

– access the OAuth credentials from the environmentThis is possible because the app is bound to the xsuaa service instance
It is required because we need the client id and secret and token-url
(what we used in postman request)
We need it in order to fetch the JWT token from xsuaa server

const VCAP_SERVICES = JSON.parse(process.env.VCAP_SERVICES)
const CREDENTIALS = VCAP_SERVICES.xsuaa[0].credentials
const OA_CLIENTID = CREDENTIALS.clientid; 

– execute an https call to fetch the token

const fetchJwtToken = function() {
...
  const options = {
  Authorization: "Basic " + Buffer.from(OA_CLIENTID + ':' + OA_SECRET).toString("base64")
...
  https.get(options, res => {
...

– use the token to call the endpoint of the provider app

const doCallEndpoint = function(){
...
   const options = {
   host:  'providerapp.cfapps.eu10.hana.ondemand.com',
   path:  '/getData',
...
   Authorization: 'Bearer ' + jwtToken
...         
   const req = https.request(options, (res) => {

– to make this procedure accessible in the cloud, we start a server and offer an endpoint

app.get('/trigger', function(req, res){  
...
app.listen(process.env.PORT, ()=>{}) 

See appendix for full file content

3.4. Deploy the client app

In folder C:\tmp_app2app\clientapp create a manifest.yml file and copy the content from the appendix section

...
- name: clientapp
  services:
    - xsuaaforclient

In the manifest we declare a dependency to the xsuaa instance created above

Call provider app from client app

After deployment, we can invoke the endpoint of our client app in a browser
…fortunately, this endpoint is not protected at all…;-)

https://clientapp.cfapps.eu10.hana.ondemand.com/trigger

As a result, we get a success message

Recap

To enable communication between 2 apps, using different xsuaa, but in same subaccount:
1. in protected app (provider), the scope must be „granted“ to the client-xsuaa
2. in using app (client), the “authorities” property is required in xs-security.json

Note:
If you need a scenario where the apps are located in different subaccounts, refer next tutorial

Diagram

Summary

Thanks for reading this blog.
I hope it has helped to solve these tedious authorization-problems

We have covered 2 scenarios:

A) Protected app is called by external user:
The app is protected with OAuth and defines a scope and a role
The external user creates his own xsuaa instance to call that API
B) Protected app is called by client app
The app is protected with OAuth and defines a scope
The client app wants to call the API and somehow needs to have that scope

Note:
More information about JWT tokens can be found in this blog post

Troublemaking

  • If the forbidden-error persists, then probably there is any typo in one of the xs-security.json files
  • As mentioned above: make sure that you enter the value of the property xsappname (copy it from xs-security file), not instance name nor app name
  • When executing the update-service command, make sure to step into the correct folder, such that you don’t e.g. update the provider-xsuaa with the content of the other client-xs-security
  • To avoid such a copy&paste error, or error with history of cmd, you can choose different names for each xs-security.json file (e.g. xs-sec-provider.json, etc)
  • Make sure that you’re running the scenario in the same subaccount
    If you need 2 subaccounts, see the next tutorial
  • When using REST client, make sure to select the appropriate grant type. User credentials do work only if user has roles
  • In case of user: didn’t forget to add role collection to IDP?
  • If the problem persists: if you’re using old client lib, it might be necessary to add the trust variable to the env of your app
    This is done by adding the following snippet to your manifest.yml
    (Alternatively, it can be added in the cockpit or with set-env command. In these cases, make sure to restart or restage your app)
    See this blog postfor detailed info.
    his variable tells the xssec validator to accept foreign clientids and identitiyzones
    manifest.yml:

    env:
      SAP_JWT_TRUST_ACL: >
        [
          {"clientid":"*", "identityzone":"*"}
        ]​

Quick Guide

App requires OAuth and scope

A) Protected app is called by external user:
We need:
1. “granted-apps” (in provider-app-xs-security.json)
2. “foreign-scope-references” (in human-user-xs-security.json)
3. Assign role to user

B) Protected app is called by client app
We need:
1. “grant-as-authority-to-apps” (in provider-app-xs-security.json)
2. “authorities” (in client-app-xs-security.json)

Links

Next Tutorial: same scenario, but with different subaccounts

Docu: SAP Help Portal

Useful info about security in this series: Blog series
OAuth: here 
JWT tokens info in this blog post
How to add custom property to JWT token
Little app router series
OAuth flow with REST client: here

Appendix: All Sample Project Files

For your convenience, see screenshot for overview about project structure

App 1: API Provider App

xs-security.json

{
  "xsappname" : "xsappforproviderapp",
  "tenant-mode" : "dedicated",
  "scopes": [{
      "name": "$XSAPPNAME.scopeforproviderapp",
      "granted-apps" : [ "$XSAPPNAME(application,xsappforhumanuser)"],
      "grant-as-authority-to-apps" : [ "$XSAPPNAME(application, xsappforclientapp)"]
  }],
  "role-templates": [ { 
      "name"                : "TheProviderRoleTemplate", 
      "default-role-name"   : "TheRoleForProviderApp",
      "scope-references"    : ["$XSAPPNAME.scopeforproviderapp"]
  }]
}

package.json

{
  "main": "server.js",
  "dependencies": {
    "@sap/xsenv": "latest",
    "@sap/xssec": "latest",
    "express": "^4.16.3",
    "passport": "^0.4.1"
  }
}

server.js

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

//configure passport
const xsuaaService = xsenv.getServices({ myXsuaa: { tag: 'xsuaa' }});
const xsuaaCredentials = xsuaaService.myXsuaa; 
const jwtStrategy = new JWTStrategy(xsuaaCredentials)

// configure express server with authentication middleware
passport.use(jwtStrategy);
const app = express();

// Middleware to read JWT sent by client
function jwtLogger(req, res, next) {
   console.log('===> Decoding auth header' )
   const jwtToken = readJwt(req)
   if(jwtToken){
      console.log('===> JWT: audiences: ' + jwtToken.aud);
      console.log('===> JWT: scopes: ' + jwtToken.scope);
      console.log('===> JWT: client_id: ' + jwtToken.client_id);
   }

   next()
}

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

// app endpoint with authorization check
app.get('/getData', function(req, res){       
   console.log('===> Endpoint has been reached. Now checking authorization')
   const MY_SCOPE = xsuaaCredentials.xsappname + '.scopeforproviderapp'// scope name copied from xs-security.json
   if(req.authInfo.checkScope(MY_SCOPE)){
      res.send('The endpoint was properly called, role available, delivering data');
   }else{
      const jwtToken = readJwt(req)
      const availableScopes = jwtToken ? jwtToken.scope : {}
   
      return res.status(403).json({
         error: 'Unauthorized',
         message: `Missing required role: <scopeforproviderapp>. Available scopes: ${availableScopes}`
     });
   }
});

const readJwt = function(req){
   const authHeader = req.headers.authorization;
   if (authHeader){
      const theJwtToken = authHeader.substring(7);
      if(theJwtToken){
         const jwtBase64Encoded = theJwtToken.split('.')[1];
         if(jwtBase64Encoded){
            const jwtDecoded = Buffer.from(jwtBase64Encoded, 'base64').toString('ascii');
            return JSON.parse(jwtDecoded);           
         }
      }
   }
}

// start server
app.listen(process.env.PORT || 8080, () => {
   console.log('Server running...')
})

manifest.yml

---
applications:
- name: providerapp
  memory: 128M
  buildpacks:
    - nodejs_buildpack
  services:
    - xsuaaforprovider
  env: 
    DEBUG: xssec:*

App 2: Client App

xs-security.json

{
  "xsappname" : "xsappforclientapp",
  "tenant-mode" : "dedicated",
  "authorities":["$XSAPPNAME(application,xsappforproviderapp).scopeforproviderapp"]
}

package.json

{
  "dependencies": {
    "express": "^4.16.3"
  }
}

server.js

const express = require('express')
const app = express()
const https = require('https');

// access credentials from environment variable (alternatively use xsenv)
const VCAP_SERVICES = JSON.parse(process.env.VCAP_SERVICES)
const CREDENTIALS = VCAP_SERVICES.xsuaa[0].credentials
//oauth
const OA_CLIENTID = CREDENTIALS.clientid; 
const OA_SECRET = CREDENTIALS.clientsecret;
const OA_ENDPOINT = CREDENTIALS.url;

// endpoint of our client app
app.get('/trigger', function(req, res){       
   doCallEndpoint()
   .then(()=>{
      res.status(202).send('Successfully called remote endpoint.');
   }).catch((error)=>{
      console.log('Error occurred while calling REST endpoint ' + error)
      res.status(500).send('Error while calling remote endpoint.');
   })
});

// helper method to call the endpoint
const doCallEndpoint = function(){
   return new Promise((resolve, reject) => {
      return fetchJwtToken()
         .then((jwtToken) => {

            const options = {
               host:  'providerapp.cfapps.eu10.hana.ondemand.com',
               path:  '/getData',
               method: 'GET',
               headers: {
                  Authorization: 'Bearer ' + jwtToken
               }
            }
            
            const req = https.request(options, (res) => {
               res.setEncoding('utf8')
               const status = res.statusCode 
               if (status !== 200 && status !== 201) {
                  return reject(new Error(`Failed to call endpoint. Error: ${status} - ${res.statusMessage}`))
               }
         
               res.on('data', () => {
                  resolve()
               })
            });
            
            req.on('error', (error) => {
               return reject({error: error})
            });
         
            req.write('done')
            req.end()   
      })
      .catch((error) => {
         reject(error)
      })
   })
}

// jwt token required for calling REST api
const fetchJwtToken = function() {
   return new Promise ((resolve, reject) => {
      const options = {
         host:  OA_ENDPOINT.replace('https://', ''),
         path: '/oauth/token?grant_type=client_credentials&response_type=token',
         headers: {
            Authorization: "Basic " + Buffer.from(OA_CLIENTID + ':' + OA_SECRET).toString("base64")
         }
      }

      https.get(options, res => {
         res.setEncoding('utf8')
         let response = ''
         res.on('data', chunk => {
           response += chunk
         })

         res.on('end', () => {
            try {
               const jwtToken = JSON.parse(response).access_token                
               resolve(jwtToken)
            } catch (error) {
               return reject(new Error('Error while fetching JWT token'))               
            }
         })
      })
      .on("error", (error) => {
         return reject({error: error})
      });
   })   
}

// Start server
app.listen(process.env.PORT || 8080, ()=>{})

manifest.yml

---
applications:
- name: clientapp
  memory: 128M
  buildpacks:
    - nodejs_buildpack
  services:
    - xsuaaforclient

Optional: Human User

xs-security.json

{
  "xsappname" : "xsappforhumanuser",
  "tenant-mode" : "dedicated",
  "foreign-scope-references": ["$XSAPPNAME(application,xsappforproviderapp).scopeforproviderapp"]
}
10 Comments
You must be Logged on to comment or reply to a post.
  • Hi Carlos Roggan ,

    Again a wonderful blog from you. Thanks for that.

    I do have some queries.

    • So if the protected app is called by the client app, it seems that it is exposed to everyone. Because it doesn’t have any security. What is the purpose of that?
    • Is there any possibility, that restricting only one user who was added in the Role-Collections part.

    Thanks,

    Badhusha.

    • Hello Badhusha Akhthaar ,
      Thank you soo much for this wonderful feedback 😉

      About your questions:
      * Why do you think it doesn’t have any security?
      It is protected with oauth (passport using JWT Strategy)
      So it requires users to be authenticated
      Furthermore it the users need to have a scope/role
      If users/apps are bound to own xsuaa instance, then the foreign-scope-mechanism is required
      The protected app allows only specific foreign xsuaa instance
      Or did I miss any point?

      *  Why should you want to write an app which can only be used by ONE user?
      Hum – I see…..maybe it is for your girlfriend only?
      It is the responsibility of an admin to assign a role collection to a group of users, or to each single user individually. That depends on the used Identity Provider. In our Trial accounts there’s the default SAP ID service.
      So it would be up to you to bribe the admin such that he only assigns the role collection to your girlfriend
      One more possibility would be to add an additional check in your app code:
      After checking if the scope is contained in the JWT token, you can check the girliename, sorry, the username property. Manually. In the code. Probably not a common way
      If you change girlfriends frequently, you can use e.g. a serverless function to provide you with the current username.
      😉
      Beg your pardon if you don’t like my humor;-)
      Cheers, Carlos 😉

      • Currently, I have deployed the client app and tried to call from the POSTMAN with NoAuth, It is returning success message(202-Accepted).

         

        Is this is how the application works?  I thought like when we call the application with Basic Auth, using the Mail id which we assigned earlier to a role,  it will send the Success response, and for others, it will send Forbidden.  
        Or I have understood in the wrong way.🤔

         

        I do like your humor 😂

        Thanks,
        Badhusha.

        /
        • Hello Badhusha Akhthaar ,
          sorry for the late reply, strange that I didn’t receive notification
          pouh – glad that you didn’t feel offended by my humor 😉

          I think I misunderstood your question.
          You’re talking about the client app.
          You’re right: this one is NOT protected at all.
          Correct.
          But that’s intended.
          The topic of the blog is, to learn how to call a PROTECTED app.
          From other app, external app.
          The “other app” (client) can be protected as well, it doesn’t make a difference for the topic of this blog.
          But it makes it much easier to follow this blog, if it is not protected.
          If you want to protect it:
          you can add the same middleware like shown in the sample of the protected app
          Then it is protected with oauth as well
          To sign in with basic auth, you can use app router (see here)
          Or you can try the native “basic auth” middleware of passport. But then you have to take care of validating the concrete user(s)

          Hope it has been understandable, my english is too poor…
          Don’t hesitate to ask again 😉

          Cheers,
          Carlos

  • Wow great blog post Carlos! Thanks for taking your time for writing it.

     

    I really like the way it is structured. I wished more of the SAP’s official documentation would be like this. Most of the time the explanation are really confusing and there is lack of practical examples.

  • Great Blog Carlos. Thanks for putting up such a detailed blog. Really helpful

     

    I am trying to do a similar scenario and following the same you did for the client App.

    However, I am stuck with the `403 Forbidden` error. When I check the logs, the scope is being passed correctly to the provider app but the user data is undefined.

    Could this be a reason for the 403 Error? 

     

    logs

    logs

     

    Would be really great if you could provide some insight on this.

    Thanks in Advance,

    Deepak

    /
    logs
    • Hi Deepak Sahu ,
      Thanks for your feedback, I’m glad that all the effort is helpful for somebody!
      Regarding the user: depending on the scenario, this property is empty.
      In case of client-credentials, which is the purpose of this blog, there’s no user-login, so it is fine that this property remains empty
      I see in your screenshot that the scope is contained in the token.
      Maybe there’s a mistake in the scope-check?
      I mean, it is the provider-app which sends the 403-Error, due to manual implementation.
      The passport library would send a 401 if the JWT token is not ok.
      So you can debug your code and check if the check does correctly compare the incoming scope
      Like this:

      const MY_SCOPE = xsuaaCredentials.xsappname + '.scopeforproviderapp'
  • Hi Carlos Roggan,

    Thanks for the nice and detailed explanation.

    In my case, I have a clientapp (MTA app with UI5, html5 repo & app-router) and I have another providerapp (MTA with xsjs module & database module) and I would like to access providerapp endpoints from my client app.

    Can you suggest me how can I access providerapp from my client UI5 app?

    Do I need to make use of destination service? (I already tried this, created a destination for providerapp with providerapp’s xsuaa client id & secret which is working, But I figured out this is wrong as client app user info is not propagated to providerapp level in this way, thus not possible to apply some database level authorizations)

    Do I need to add a nodejs module to my client app MTA along with existing UI5 to implement as you explained? or Is there any other way to expose my providerapp to my clientapp to router my ui5 backend calls?

    Thanks in advance for your suggestion.

    Regards,

    Suchen.