Skip to Content
Technical Articles
Author's profile photo Carlos Roggan

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 can be done in the cockpit – or 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 2.0
Understanding of OAuth for dummies like me.

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

Security Glossary.

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"]
}

Assigned Tags

      42 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Badhusha Akhthaar
      Badhusha Akhthaar

      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.

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      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 😉

      Author's profile photo Badhusha Akhthaar
      Badhusha Akhthaar

      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.

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      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

      Author's profile photo Luisa Grigorescu
      Luisa Grigorescu

      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.

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi Luisa Grigorescu  ,
      Thanks so much for the feedback…!!!
      Glad to hear that it helps and encouraging for continuing this way…!
      Cheers,
      Carlos

      Author's profile photo Deepak Sahu
      Deepak Sahu

      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

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      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'
      Author's profile photo Suchen Oguri
      Suchen Oguri

      Hi https://people.sap.com/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.

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hello Suchen Oguri ,
      Thanks for your nice feedback
      I wanted to suggest to follow-up in personal chat, but I've found your question here
      I've tried an answer there, please check if it helps
      Kind Regards,
      Carlos

      Author's profile photo Kush Sharma
      Kush Sharma

      Hi Carlos,

      What if:

      Provider app xsuaa has "granted-app" for humanuser to access all APIsbut xsuaa plan is "broker",

      I then create my own xsuaa of "application" plan with "foreign-scope-references" and use the url,client_id and secret to generate a token and access all provider app Apis?

       

      Would it work if 2 xsuaa are of different plans?

       

      Regards,

      Kush Sharma

       

      Author's profile photo Vladimír Balko
      Vladimír Balko

      Regarding:

      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

       

      Now user can update service instance also in cockpit. Thank you SAP

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi Vladimír Balko ,

      thanks for pointing it out (I've adjusted) and thanks for the nice feedback 😉

      Please keep commenting - it is helpful!

      Cheers,

      Carlos

      Author's profile photo chris pure
      chris pure

      Hi Carlos,

      In my application. I have a approuter  -- ui5 -- backend service  setup.  UI5 to backend is via ODATA api communication.  While testing in local , I noticed the possibility of  end user  getting the cookie from browser - network tab  and make post/delete operations on backend service from rest clients  like postman.

      I want to restrict end user calling the api directly and let only the UI call backend service.

      I have tried  giving only GET access to the user in the backend service , but the UI was not able to do POST operation on user request.

      I'm new here, kind of lost on this. Please share your thoughts. Thanks in advance.

       

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hello chris pure ,

      I'm a bit confused: your UI5 application is similar component like postman, just more convenient. I mean, it acts on behalf of the user. The user comes with account priviledges and roles, tailored to execute operations on backend data via OData service. The UI is just convenience.
      Maybe it would help to distinguish roles, for protecting delete operations? Such that only admin can do deletion?
      Otherwise you would need to do workarounds, like restricting the delete access only via client-credentials. You would define a scope for deletion - but not create role for that scope.
      Then you would have to programmatically call that endpoint in your ui5-app, programmatically doing the oauth flow to obtain jwt token for client-credentials grant.
      I'm sorry, I'm not architect, maybe there's a more elegant solution which I'm not aware of.

      Kind Regards,

      Carlos

      Author's profile photo chris pure
      chris pure

      Thank you Carlos,  for your inputs. I will look in to more and get back to you.

      One more question , for my application, we have approuter in front of ui5 application. Curious to know if wee need xs app.json in router and also in ui5/webapp folders ?

      what is the difference between putting UI related files in approuter  vs  deploying UI app separately  in html5 repository  ?  Thanks in advance.

       

       

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hello chris pure

      the application router is an existing component which you can deploy standalone to Cloud Foundry. It is configured with xs-app.json.
      As such, this file is part of approuter and it is required by approuter.
      Approuter refuses to start if the file is not present in the expected location (BTW, approuter can be configured programmatically)
      You can put approuter in front of a protected REST service, so the approuter does the OAuth flow for the user.
      The most frequent use case is to put approuter in front of a UI5 application
      And I guess in most cases there's only one approuter for one UI project.
      (I mean, approuter not used standalone)
      In this case, it is common behavior to use embedded approuter, together with ui5 app folder.
      Like that, you can easily point to the webapp as starting point for the first route.

      I went through little understanding process and I shared my learning here

      Kind regards,
      Carlos

       

      Author's profile photo Chao Chen
      Chao Chen

      Hi Carlos:

      Thanks a lot for your blog post. It is life saving.

      I would like to ask another scenario:

      If the client application is not hosted in SAP BTP. Is there any solution to complete OAuth2 authentication flow and consume RESTful API hosted in SAP BTP?

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi Chao Chen , sorry for the late reply, I must have missed the notification ;(
      For remote access to a resource in BTP, you can create a service key for xsuaa service. This gives you the credentials to access that service without binding. It works for client credentials flow

      Author's profile photo Ashish Singh
      Ashish Singh

      Thanks, Carlos, for such a wonderful explanation! It has certainly enhanced my understanding about how xsuaa security works! And the added humour makes the learning enjoyable 😉

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Thank you so much for this fantastic feedback Ashish Singh that is really helpful and encouraging 😉

      Author's profile photo Kanika Malhotra
      Kanika Malhotra

      Hi Carlos Roggan

      If we have a CAP service which has roles and instance -based authorization on it.

      And we need to access the same API from CPI tenant, how can we achieve the same?

      Currently we are using client id and secret from cpi to access the cap service.

      Thanks

      Kanika

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi Kanika Malhotra ,

      I'm not familiar with CAP, but I think this blog post can help you:

      https://blogs.sap.com/2022/03/15/403-forbidden-scope-missing-in-jwt-aaargh/

      In brief, you need to assign the scope to your own xsuaa-instance.
      Then enter clientid/secret in CPI security artifact.
      When it fetches the JWT token, the token will contain the scope.

      Hope this helps,

      Kind Regards,
      Carlos

      Author's profile photo Durgaprasad Sreedhara
      Durgaprasad Sreedhara

      Hi Carlos,

       

      Thank you for this detailed blog. Very detailed. I am trying to implement something very similar to this, but both the apps are nodejs CDS apps. Trying to follow the this help

      Two questions:

      1. Does the provider app need to be changed for every consumer app ? This particular reference in grant-as-authority-to-apps hardcodes the client app.
      2. Per help above, declaration of the provider resource should be of type "configuration". The example client is using the  JWT tokens to connect with the provider. Does CDS do this automatically?
      3.  How should the consumer's package.json CDS refer to consumer configuration declaration?

      Thank you for help

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi,

      as I'm not familiar with CDS, I can only answer first question:
      yes, of course, granting a scope is like assigning a role to a user, so it is an explicit statement.
      However, you don't type the name of an application, you type the name of the oauth client, which is more flexible

      Kind Regards,

      Carlos

      Author's profile photo Durgaprasad Sreedhara
      Durgaprasad Sreedhara

      Thank you Carlos Roggan I will try your example as is and see how it works.

      When you say add the name of "oauth client" What do you mean by that? I have a provider service? that is publishing the service. The "consumer app" is the client (oauth client). Hardcoding the oauth client itself. What would the name of the oauth client be ?

      What I would like to build is a "service provider" in a space in a tenant. Any consumer in the same space and tenant are free to subscribe to the service and use it without changing the provider (unless a change is required in the provider functionality).

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Sorry if I've confused you.
      When you create an instance of xsuaa service, then you get the client id and client secret, right?
      So this ID identifies an "OAuth client" that is registered at the "Authorization Server" (XSUAA server).
      As such, we can use the term "oauth client" when talking about xsuaa-service-instance.
      When you create an instance, you can configure it with xs-security.json file.
      There, you can configure the property xsappname (otherwise it would be generated).
      This is what I meant with the name of the oauth-client.

      Kind Regards,
      Carlos

      Author's profile photo Sreehari V Pillai
      Sreehari V Pillai

      Hi Carlos - this blog is a saver

      Can I ask you a quick one ? My xsappname is dynamic - like this

        - name: uaa-product-management
          type: org.cloudfoundry.managed-service
          parameters:
            service: xsuaa
            service-plan: application
            path: ./xs-security.json
            config:
              xsappname: 'com-uaa-prd-${org}-${space}'
              tenant-mode: shared
      
      
      

       

      Can I then , specify the app names in  grant-as-authority , grant-as-apps app names in yaml file , so that this will be populated dynamically ?

       

       

      Sree

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi Sreehari V Pillai

      Thank you for your feedback !

      About your question, this is MTA-semantic and I'm not familiar with it, unfortunately.
      As far as I understand, it allows for abstraction of deploy landscapes, resulting in a dynamic replacement of variables during deployment.
      Which would mean that you could use the placeholders also for grant statement.
      But this is just a guess
      pls let us know once you've tried it

      Cheers,
      Carlos

      Author's profile photo Sreehari V Pillai
      Sreehari V Pillai

      Sure ill try this and update here . I am pessimistic here , as its not given in yaml but in xs-security.json . Ill be surprised if variables in json file is updated dynamically .

      sree

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      maybe I didn't get your question properly: are you using 2 files, mta.yaml and xs-security.json? Or only the mta.yml as in your snippet?
      During deploy, the mta-deployer will generate an instance of xsuaa, right?
      And insert the variables-values
      You can view the result after deployment, if you view the credentials of the generated xsuaa-instance

      Author's profile photo Sreehari V Pillai
      Sreehari V Pillai

      Sorry If I confused you.

      I have a calling Application ( App A ) has an xs-security.json file. In its mta.yaml file , xsappname is set as : callingapp-${org}-${space}

      when I deploy the app , mta.yaml variables shall be replaced and the xsuaa will use dynamically generated xsapp name

       

      I do the same for called app as well . Now the problem is , I am maintaing the scope in xs-security.sjon , where I put called app's xsappname without runtime variables. I tried this now and deployment failed . So there must be an option to maintain scopes in yaml files , or to not to use dynamic app names in yaml .

      I posted a separate question here 

      sree

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      ok. now I agree with your pessimism. I xs-security you cannot reuse mta-variables
      As I'm not familiar with mta, I cannot answer, but I would assume that you can config options for scopes etc in mta-params-section

      Author's profile photo Sreehari V Pillai
      Sreehari V Pillai

      Do u have an example for this ? ( config param )

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      sorry, this was just a guess, I'm not familiar with mta

      Author's profile photo Sreehari V Pillai
      Sreehari V Pillai

      Hay .

       

      Why don't we simply user "oAuth2 User toke exchange" authentication type in the destination ?
      Create destination of called app using "user token exchange" . Mention it's client Id and secret.

      we can avoid scope mapping in caller application . I tested it with 2 CAP applications and it works smooth

       

      Sree

       

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hello Sreehari V Pillai ,

      you can contact me directly via personal message to discuss further your scenario.
      What I can say to help to clarify:

      • You can always call any backend from anywhere, e.g. postman, if you enter the backend credentials
      • However, even this simple scenario doesn't add automatically the required scope
        To get the scope, you need to add a GRANT statement to your xs-security (the mentioned authorities statement)
      • So if you want to avoid work, you can use this kind of "hard-coded" credentials
      • Now, the token exchange is very similar, because you specify the backend clientid/secret
        and in addition you add the user-token in order to add the user-info
      • However, you cannot just add ANY user-token, it must be valid for the backend application Means: same subaccount. This restriction can be overcome using the cross-subaccount grant, as explained in next blog
      • Background: the backend-client id must be contained in the aud claim of the frontend-jwt-token
      • As such, even if you use token exchange, you need to specify the GRANT between the 2 instances of xsuaa.
      • 2 instances of xsuaa are basically 2 oauth-clients.
        A jwt token issued for one client cannot be accepted by any different client
      • The token exchange doesn't grant the required scope during exchange. It just assigns.
        So the scope must be granted beforehand
      • Summary: 
        Grant  scope must be always there, either as grant statement in the security-descriptor.
        Or, what you're doing, adding the grant by assigning the role to a user
      • What is more convenient? The manual admin-way? 
        The descriptor-way is more convenient, because done once and valid for all users

      Unfortunately, I don't find the time now to verify it, so it is just my strong guess

      Thanks and kind regards,

      Carlos

       

      Author's profile photo Sreehari V Pillai
      Sreehari V Pillai

      Perhaps , what you explained in the blog is different than the (simple) scenario I was trying to achieve .

      Scenario 

      I have 2 micro services ( CAP is irrelevant here ).  Order-App and Masterdata-App . Order-App has its own xsuaa instance. A user must have scope "order-create" to create an order from the frontend . To read the master data from Masterdata-App , the caller must have a scope "mdm-read" scope. Here , "Order-app" makes an http request to "Masterdata-App"'s Odata endpoint ( from nodejs ).

      Solution 

      I created a destination in my sub-account "MasterData" . Type being "OAuth2userTokenExchange" .
      from "Order-App" , I connect to this destination , propagating the token issued to the froentend by the xsuaa of "Order-app" ( thats where the user apparently logged in ) .

      Destination service then generated another token for Masterdata-App for the logged in user , Consumed the service propagating this token , and read the data .

      The frontend user has both the roles assigned to him.
      Its an inter app communication scenario , propagating the users' scope between them . Just like a trusted RFC call in ABAP perspective.

      Author's profile photo Sreehari V Pillai
      Sreehari V Pillai

      detailed project uploaded to git

       

      https://github.com/sreehari-pillai-atom/cf-interapp-conn/tree/main

       

      Sreehari

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      One comment I have about token exchange:
      Usually, what you want to achieve:
      User uses Orderapp and has the required rolecollection for orderapp, which is user-facing.
      User does not need to know about backend-scopes, like masterdata, as these are not user-facing data.
      It is orderapp-internal knowledge, that orderapp calls some other external services.
      User should not be bothered with that.
      THEREFORE, the tokenexchange was invented, I believe.
      The user has orderrole - and orderapp does tokenexchange in order to get the masterdata scope (not role)
      Masterdata is not user-facing, so there are no role-templates there
      This is what I would expect as normal use case.
      Of course, application setup can be different and users might need to know about both apps, why not. I mean, a user-facing app can call a second user-facing app.
      However, I would suggest to carefully overthink the design in that case.
      E.g. it might be better to have a third module, head-less service, which is used by both user-facing apps. As such, avoiding that users need both roles. Always better to have focussed narrow role-assignment.

      Just my 2 cents

      Author's profile photo Sreehari V Pillai
      Sreehari V Pillai

      Excellent, thanks for this - I was mixing up the requirement of needing to have one app containing scope references of other apps .

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      always nice talking to you 😉