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

outdated –> SAP_JWT_TRUST_ACL <-- outdated

SAP Cloud Platform Security:

SAP_JWT_TRUST_ACL

is obsolete
and doesn’t work anymore

since May 2020 (roughly)

Quicklinks:
Quick Guide
Sample Code

Intro

I assume you’ve found this blog post because you’re troubleshooting an error or because you’ve found this setting in an app and wondering about it
Personally, in my case, I was searching for solution of a problem – and found below information.
Since this variable SAP_JWT_TRUST_ACL is broadly used is docu, blog posts, applications etc, I thought it makes sense to share below information with you.
Please note that I’m not a security expert and please accept my apologies for vague and imprecise  explanations.

Overview

For better understanding, we’re going through a long tutorial, describing why the setting was necessary and why it is not needed anymore

Part 1:
Simple scenario
Hands-On 1: app2app with one instance of XSUAA

Part 2:
Scenario requires SAP_JWT_TRUST_ACL
Hands- on 2: app2app with 2 instances of XSUAA -> error

Part 3:
Same scenario with new mechanism
Hands- on 3: app2app with 2 instances of XSUAA -> solved

Background

In SAP Cloud Platform, we use OAuth 2.0 to protect applications
In general, security is a huge and advanced topic.
In this blog post, I’m focusing on a small portion:
security between applications
With other words:
app2app scenario
With other words:
client-credentials

To establish security for an application, an application developer has to do the following:

  • Create an instance of XSUAA
  • Bind the instance to the application
  • Write some security-related code

More details:

  • XSUAA acts as “Authorization Server” (in terms of OAuth) which generates a JWT token
  • This access token is used to call the secured app
  • The secured app is bound to an instance of XSUA
  • The binding information contains e.g. the “client ID”, “xsappname” etc
  • The app uses that info to validate the JWT token
  • Properties  “clientid” and “secret” are used as credentials for contacting the “Authorization Server” (xsuaa)

Part 1: Simple app-to-app scenario

We can call it app2app or app2service, just to make clear that this is not user centric scenario, no interactive user-login, no roles, no role collections, no Identity Provider
No friendly ppl
Only apps
Cool aps

Scenario 2: app-to-app with 1 instance of XSUAA

The flow:

“We” call the authorization server with client/secret and as response, we get a JWT token
“We” use this token to call the protected app
The protected app validates the token and is happy to send a success- response

What is that mystic JWT token ?
To make it even more mystic, it is pronounced as “jott token” (you must have heard that)
The token is a long trashy string consisting of 3 encoded parts, separated with dots
The middle part is the payload, JSON formatted
The payload contains info about

  • client ID,
  • scopes (roles)
  • user eMail
  • audiences,
  • identity zone

etc

Validation

The protected app is protected because it validates the incoming access token
Validation is mostly done by the xssec client library, provided by SAP
The xssec takes the JWT token, decodes, parses and checks it (so-called offline validaton)

One of the first things to check:
The client ID contained in the token: is it the same like the client ID of the bound xsuaa instance?

In our simple example flow, yes, it is the same:
To get the JWT, we’ve contacted the same xsuaa instance, which is bound to the protected app

Above diagram shows:
We have 2 applications, bound to one instance of XSUAA
The provider app is provides a protected endpoint which requires a valid JWT token
The client app consumes the endpoint
To do so, the client app uses binding info to contact XSUAA to get a JWT token
To access the endpoint, the client app sends the JWT token to the provider app
To validate the token, the provider app uses binding info

Hands-on 1: app2app with one instance of XSUAA

This hands-on is useless, but nevertheless:
Let’s build that simple scenario flow. The code can be found in the appendix 2
Note that only the security configuration is different, you need to copy it from here

1. Security Configuration

We create an instance of XSUAA with very basic params.
As good practice, we store the params in a securitydescriptor file, typically called xs-security.json, but in my tutorials I call it differently, today the file is called
xs-securityforprovider.json

{
  "xsappname" : "xsappforprovider"
}

Note that I like to use stupid names.
You’ve already noticed that file names are stupid
Now properties are stupid
In this case, I want that the name makes clear that it is the value of the property “xsappname”

Now create instance based on above stupid param:
Jump into the folder C:\tmp_app2app\providerapp and execute

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

2. Provider App

Afterwards we can create the provider app according to the sample code in the appendix 2

3. Client App

The client app has one difference compared to the sample code in the appendix 2
The difference is that in our first hands-on we use the same instance of xsuaa.
As such, the service name in the manifest has to be adapted

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

4. Deploy and Run

After deploying both apps (in my example, I deployed to my Trial account), we call the endpoint of our client app

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

The clientapp code uses the client ID from xsuaa credentials from binding to get a JWT token
The provider app prints some boring but relevant info to the logs:

What do we see here?

The first line prints the value of the variable xsappname from the xs-security.json file
We can see that this name is used when generating a client ID
We can see that the client ID which is used by the provider app is the same as the client ID sent by the client app in the JWT token
This doesn’t surprise us, because we know that the JWT token has been issued by the same instance of xsuaa
We can see that there aren’t specific scopes/authorities (we haven’t defined)
Finally, the interesting property: “audiences”. We can see that the client app sends the client ID in the audience property of the JWT token
Why interesting?
We’ll come to it later
How boring

5. Ehm??

Any problem?
No problem.
Only that we haven’t talked at all about that JWT_WHATEVER_TRUST???

True.

Why was SAP_JWT_TRUST_ACL required?

It was required in a scenario like the following one:

Part 2: Scenario requiring (legacy) SAP_JWT_TRUST_ACL

To answer this question, let’s have a look at a scenario with 2 instances of XSUAA

Why are there 2 instances of XSUAA?
There can be several reasons:
E.g. the provider app can be a user-defined micro-service
Or the provider app can be a re-use service provided centrally in your tenant
Or the provider app can be living in a different account (see here)

Scenario 2: app2app with 2 instances of XSUAA

In this scenario we can expect problems
Brrrrr….
Reason:
The client app will obtain a JWT token which contains a new and different client ID, because it is issued by a different instance of xsuaa
The provider app will validate the JWT token and will find a client ID which is unknown to it

Let’s see it in concrete example

Hands- on 2: app2app with 2 instances of XSUAA -> error

1. Provider App

No change at all

2. Client App

Define Security Configuration

Now we need a security configuration for the client app

We create a file with (stupid) name xs-securityforclient.json in folder C:\tmp_app2app\clientapp
We don’t copy the content from the appendix.
Instead, we use the following (useless) content:

{
  "xsappname" : "xsappforclient"
}

Then we create the second instance of XSUAA:

cf cs xsuaa application xsuaaforclient -c xs-securityforclient.json

Modify Client App

Afterwards we change again the manifest.yml file, because now we want to use this new instance:

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

Before we push the app, we should delete the existing app (or delete the binding)
Reason:
Since we deployed the client app with a binding, the new binding would be added, which would cause trouble.
So let’s just delete the deployed app

cf d clientapp -r -f

Then push again

3. Run the apps

Now, when calling our client app

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

Result: Error
The request is rejected by xssec
Reason: In the log, we can read the reason:

[ERR Jwt token with audience: [ ‘uaa’, ‘sb-xsappforclient!t53896’ ] is not issued for these clientIds: [ ‘sb-xsappforprovider!t53896’, ‘xsappforprovider!t53896’ ]

And this is (finally) the first relevant learning of this blog post:
The security library checks if the incoming JWT token contains the client ID of its own binding
If it is different, the access to the resource is forbidden, the call is not allowed

Now we check our own log output:

We can see:
The client ID of the protected app is: sb-xsappforprovider!t53896
The client ID contained in the JWT, sent by clientapp is sb-xsappforclient!t53896
They’re different
Obviously

However:
The scenario is legal and necessary.
In every scenario where an application has to call a re-use service and where the client app cannot be bound to the same instance of xsuaa
As said above

The Solution (Legacy)

The solution until now was:
The provider app had to define a white-list where the allowed consumers were listed
This list (ACL) was provided as environment variable of the application in Cloud Foundry
The name of the env variable was the well known SAP_JWT_TRUST_ACL
The value was a list of client IDs, and also different identity zone IDs could be maintained
Remember: ACL stands for “Access Control List”

I guess we all felt a bit strange when learning this concept – it somehow looked a bit odd.
Why odd?
Odd, because it was a different, unexpected concept: security was defined in the xs-security.json file and ACL (security-relevant as well) was defined somewhere else

Also, when transporting an app to different landscape (dev->prod) then the admin mustn’t forget to adapt the ACL env var to new client IDs etc

As such, it has been a good idea to replace that mechanism
Agree

Part 3: The new mechanism

The question is:
How to declare that a foreign client is allowed to access my protected resource?
The answer is:
Using the aud” claim

What’s that: the aud claim ???
This is one of the so-called “claims” contained in a JWT token

What’s that: claim ???
As we know, the JWT token is a very small package carrying compact information, designed to be small and easy to consume
That’s why it is encoded and JSON-formatted
A JSON object consists of properties and in case of JWT, these properties are called “claims”
They’re like statements about the desired access of the “bearer” (e.g. the user or client)

Some of the claims contained in a JWT token are default, predefined, so-called “registered claims”, others are added by SAP, are “public claims”
Since the JWT is meant to be small, the claim names are small as well, reduced to 3 characters
In the appendix 1, I’ve put together some useful info about claims

claims are e.g.:

  • iss: the issuer of the JWT token
  • iat: issued at, so we can know if the token is getting old
  • sub: subject, the owner of the token, in our case the client ID
  • exp: the expiration time

All that makes sense, right?
Yes
And also the predefined aud, which stands for “audience”
This claim contains the info about who is meant to receive the token

Example scenario:
You have a “cat-snack”, this is the “token” with tasty ID inside
And there is a cat, watching the snack: that’s the “audience”
Close your eyes
Open your eyes
Now the ID is in the audience
And: scenario works only if the cat is in a good mood and “accepts” the token

OK?

Another try:
In the JWT token, …
…the “client ID” is the consuming app which sends the token
…the “audience” is the providerapp which receives and validates the token

Validation:

The provider app validates the JWT token (with the help of the xssec lib)
Now that a foreign Client ID is not maintained in the TRUST_BULLSH…ACL anymore, it has to be maintained in the audience

Let’s have a look again at the failing scenario above:

The providerapp receives a JWT token with audience as …xsappforclient…
This is something totally unknown
As such the access is refused

Fix:
We have to somehow get the …xsappforprovider into the aud
Yes, but how?

How to maintain the required aud ???

We cannot maintain it like we maintained the ACL variable
Let’s phrase it positively:
We don’t NEED to maintain it anymore
That’s an advantage 😉

The aud claim is automatically filled by XSUAA

So how to solve our problem?
We can influence:
The additional entries in aud are derived from scopes

Scopes???

Scope is a powerful mechanism to control how can access what resource and functionality

Example:
Reading content of an API is allowed for many users, while deleting an entry is allowed only for admins
In case of human users, they get a “role” assigned
In case of apps, they get a scope “granted”

The protected app will check if the JWT token contains the required scope

In our scenario, an API provider app is called by a consuming app (hot human user)
If the provider app requires a scope, then the client app needs a “grant”
I described all that mechanism in this blog post

As such, the provider app needs to know the client, so it can grant the scope to it
If the client has that scope, then it is allowed to call the providerapp
OK, that’s quite obvious
And because it is so obvious, it is possible to automatically enter the …providerapp… into the audience
Surprised?
It is easy

With other words:

If
provider app grants “provider”-scope to client
then
client app gets JWT token with “provider” in the aud

Nice, but how to do the “grant” thing?

This is done while configuring the involved instances of XSUAA
Configuration is done with params that are usually stored in a file with common name xs-security.json
The name can be arbitrary, as it is passed to the create or update command

When we created the XSUAA instance for providerapp above, we didn’t enter anything relevant:

{
   "xsappname" : "xsappforprovider"
}

The provider app was protected, but didn’t require a scope
Now we need to define a scope
In addition, we need to grant that scope to the consuming app

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


And one more step is required:
The client app has to explicitly accept the granted scope

"authorities":["$XSAPPNAME(application,xsappforprovider).scopeforprovider"]

And the result?

As mentioned, as soon as the consuming client app gets the scope, it also automatically gets the audience
But there’s an important detail
The scope name is not completely filled into the aud claim
The scope is not completely equal to the audience

The whole mechanism relies on one fact:
When defining the scope, we concatenate the $XSAPPNAME with a dot and with the scopename

“name”: “$XSAPPNAME.<scopename>”

What is $XSAPPNAME ?

Basically, it is the value of the property xsappname which we define as we like and we can refer to it to avoid typos

"xsappname" : "xsappforproviderapp",
"scopes": [{
   "name": "$XSAPPNAME.scopeforproviderapp",

But it is more:
XSUAA generates a full xsappname at runtime, we’ve seen it in the screenshot above:

xsappforprovider!t53896

We cannot know the fully generated name, as such, we MUST use the variable when concatenating  the scope
At runtime, the full scope name will look like this:

xsappforprovider!t53896.scopeforprovider

Why we must concatenate at all?

First of all, this mechanism makes the scopes unique.
Furthermore, to generate the audience, the scopename is removed, so basically the $XSAPPNAME is put into the aud

Note:
Scope names can contain more dots, e.g. to add namespaces
So the audience can be longer
But the xssec takes care of properly validating and taking dots into account

Note:
Scope names aren’t forced to use the naming convention $XSAPPNAME.<scopename>
However, if we don’t do that, we cannot get this scenario running

Example:
Go into town center and show $$
You will immediately have audience

To phrase it differently:
No $ no aud

Small Recap

In the protected provider app we define a scope, which is required for access
We also grant the scope to the client app
In  the client app, we accept the grant
As a consequence, the JWT token, which is sent by the clientapp to the providerapp, will have the providerapp in the aud
As final consequence, the access is allowed
And remember: no $ no aud

Hands- on 3: app2app with 2 instances of XSUAA -> solved

To fix the scenario in the new way, we have to modify the 2 security descriptors and update the instances of XSUAA

1. Modify XSUAA of provider app

Modify the security descriptor xs-securityforprovider.json
in folder C:\tmp_app2app\providerapp
Add scope and grant

{
"xsappname" : "xsappforprovider",
   "scopes": [{
      "name": "$XSAPPNAME.scopeforprovider",
      "grant-as-authority-to-apps" : [ "$XSAPPNAME(application, xsappforclient)"]
   }]
}

Now modify the service instance:
Jump into the folder “providerapp” and run the following command

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

2. Modify XSUAA of client app

Add “authorities”  statement to file xs-securityforclient.json in folder C:\tmp_app2app\clientapp

{
   "xsappname" : "xsappforclient",
   "authorities":["$XSAPPNAME(application,xsappforprovider).scopeforprovider"]
}

Now modify the service instance:
Jump into the folder “clientapp” and run the following command

cf update-service xsuaaforclient -c xs-securityforclient.json

3. No deploy but run

After updating both instances of XSUAA, we can assume that we get a “better” JWT token (tasty cat food)
No change to the applications required
So we can invoke the client app

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

Enjoy the success message…
And view the logs:

We can see 2 interesting changes:
The bearer token now contains the granted scope
The aud now contains the $XSAPPNAME of the providerapp

When the providerapp receives the JWT token and xssec validates the audience, it finds its own $XSAPPNAME
This is not unknown, thus happy to go through the security control

That’s all for today.

Conclusion

We MUST provide a scope
We MUST use the variable $XSAPPNAME in scope name
We CAN remove the variable SAP_JWT_TRUST_ACL from your env (it is ignored anyways)

A scenario where only authentication with no authorization is desired: that’s not possible anymore

Quick Guide

Scenario:
Secure communication from one app to second app

Procedure:
Second app defines scope and grants it to first app

"scopes": [{
    "name": "$XSAPPNAME.scopeforprovider",
    "grant-as-authority-to-apps" : [ "$XSAPPNAME(application, xsappforclient)"]

First app accepts the grant in “authorities”

"authorities":["$XSAPPNAME(application,xsappforprovider).scopeforprovider"]

Result:
JWT token contains the target $XSAPPNAME in the audience claim which is validated by target app
Environment variable SAP_JWT_TRUST_ACL is NOT required anymore

Links

Related info
OAuth intro
OAuth flow with REST client
App to app security
App to app security accross subaccounts

JWT
How to add custom properties to JWT
jwt.io
View content of jwt token: https://jwt.io/ -> Debugger
Spec
More spec
Even more spec

SAP Help Portal
Security Descriptor Syntax
Granting scope access to different app

TechEd self-study material
GitHub

Node client modules at npm
xssec
xsenv

Java libraries at github
Cloud Security XSUAA integration

Security Glossary.

Appendix 1: claims

For your convenience, I’ve listed claims and explanations.
The content is copied from the specs (see links section)
The list contains registered claims and public claims

jti
The “jti” (JWT ID) claim provides a unique identifier for the JWT.
The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well.  The “jti” claim can be used to prevent the JWT from being replayed.  The “jti” value is a case-sensitive string.  Use of this claim is OPTIONAL.

sub
The “sub” (subject) claim identifies the principal that is the subject of the JWT.  The claims in a JWT are normally statements about the subject.  The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique.
The processing of this claim is generally application specific.  The “sub” value is a case-sensitive string containing a StringOrURI value.  Use of this claim is OPTIONAL.

scope
The value of the “scope” claim is a JSON string containing a space-separated list of scopes associated with the token

client_id
The “client_id” claim carries the client identifier of the OAuth 2.0 client that requested the token.

azp
Authorized party – the party to which the ID Token was issued

iat
The “iat” (issued at) claim identifies the time at which the JWT was issued.  This claim can be used to determine the age of the JWT.  Its value MUST be a number containing a NumericDate value.  Use of this claim is OPTIONAL.

exp
The “exp” (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.  The processing of the “exp” claim requires that the current date/time MUST be before the expiration date/time listed in the “exp” claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew.  Its value MUST be a number containing a NumericDate value.  Use of this claim is OPTIONAL.

iss
The “iss” (issuer) claim identifies the principal that issued the JWT.  The processing of this claim is generally application specific. The “iss” value is a case-sensitive string containing a StringOrURI value.  Use of this claim is OPTIONAL.

aud
The “aud” (audience) claim identifies the recipients that the JWT is intended for.  Each principal intended to process the JWT MUST identify itself with a value in the audience claim.  If the principal processing the claim does not identify itself with a value in the “aud” claim when this claim is present, then the JWT MUST be rejected.  In the general case, the “aud” value is an array of case-sensitive strings, each containing a StringOrURI value.  In the special case when the JWT has one audience, the “aud” value MAY be a single case-sensitive string containing a StringOrURI value.  The interpretation of audience values is generally application specific.
Use of this claim is OPTIONAL.

And:
Other properties which are available in a user centric scenario:
Logon name
Given name
Family name
email

For your convenience find below an example for the payload of a JWT token in my example in Trial account

{“jti”:”1234567847b448d79d3d00c14a733265″,
“ext_attr”:{
“enhancer”:”XSUAA”,
“subaccountid”:”1234abcd-bdb9-42dd-a563-fce0ccb976bc”,
“zdn”:”1234trial”},
“sub”:”sb-xsappforprovider!t53896″,
“authorities”:[“uaa.resource”],
“scope”:[“uaa.resource”],
“client_id”:”sb-xsappforprovider!t53896″,
“cid”:”sb-xsappforprovider!t53896″,
“azp”:”sb-xsappforprovider!t53896″,
“grant_type”:”client_credentials”,
“rev_sig”:”6fbad123″,
“iat”:1598964507,
“exp”:1599007707,
“iss”:”https://1234trial.authentication.eu10.hana.ondemand.com/oauth/token”,
“zid”:”1234abcd-bdb9-42dd-a563-1234abcd 76bc”,
“aud”:[“sb-xsappforprovider!t53896″,”uaa”]}

Appendix 2: All Sample Project Files

For your convenience, see screenshot for overview about project structure

App 1: API Provider App

xs-securityforprovider.json

{
  "xsappname" : "xsappforprovider",
  "scopes": [{
    "name": "$XSAPPNAME.scopeforprovider",
    "grant-as-authority-to-apps" : [ "$XSAPPNAME(application, xsappforclient)"]
  }]
}

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;
const xsuaaCredentials = xsenv.getServices({ myXsuaa: { tag: 'xsuaa' }}).myXsuaa; 
passport.use(new JWTStrategy(xsuaaCredentials));
const app = express();

// Middleware to read JWT sent by JobScheduler
function jwtLogger(req, res, next) {
   console.log('===> Binding: $XSAPPNAME: ' + xsuaaCredentials.xsappname)
   console.log('===> Binding: clientid: ' + xsuaaCredentials.clientid)
   console.log('===> Decoding JWT token sent by clientapp' )
   const authHeader = req.headers.authorization;
   if (authHeader){
      const theJwtToken = authHeader.substring(7);
      if(theJwtToken){
         const jwtBase64Encoded = theJwtToken.split('.')[1];
         const jwtDecoded = Buffer.from(jwtBase64Encoded, 'base64').toString('ascii');
         const jwtJson = JSON.parse(jwtDecoded)                     
         console.log('===> JWT: audiences: ');
         jwtJson.aud.forEach(entry => console.log(`          -> ${entry}`) );
         console.log('===> JWT: scopes: ' + jwtJson.scope);
         console.log('===> JWT: authorities: ' + jwtJson.authorities);            
         console.log('===> JWT: client_id: ' + jwtJson.client_id);     
      }
   }
   next()
}

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

app.get('/protected', function(req, res){       
   res.send('The endpoint was reached, not authorization check');
});

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

manifest.yml

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

App 2: Client App

xs-securityforclient.json

{
  "xsappname" : "xsappforclient",
  "authorities":["$XSAPPNAME(application,xsappforprovider).scopeforprovider"]
}

package.json

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

server.js

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

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

// 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:  '/protected',
               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:  CREDENTIALS.url.replace('https://', ''),
         path: '/oauth/token?grant_type=client_credentials&response_type=token',
         headers: {
            Authorization: "Basic " + Buffer.from(CREDENTIALS.clientid + ':' + CREDENTIALS.clientsecret).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

 

Assigned Tags

      15 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Michael Shea
      Michael Shea

      Thanks for pointing this out, Carlos. We are working on adding this note to the documentation wherever we can find references to this parameter:

      The client libraries (java-security, spring-xsuaa, and container security api for node.js >= 3.0.6) haveb een updated. When using these libraries, setting the parameter `SAP_JWT_TRUST_ACL` has become obsolete. This comes with a change regarding scopes. For a business application A that wants to call an application B, it's now mandatory that the application B grants at least one scope to the calling business application A. You can grant scopes with the `xs-security.json` file. For additional information, refer to the Application Security Descriptor Configuration Syntax, specifically the sections referencing the application and authorities.

      Author's profile photo Rafael Zanetti
      Rafael Zanetti

      Hi,

       

      I followed all steps and the authentication worked (YAY!!!)

      However, I need to add the logon name to the access token.

      Is it possible?

      BR,
      Rafael

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

      Hi Rafael Zanetti , GREAT !! Good that it worked 😉
      About the logon name: above scenario is about handling access between apps, client-credentials, there's no human user. The logon information of user (name, email, and more) in the JWT token are filled in a user-centric scenario.
      What you can do:
      Add an approuter. Bind it to the xsuaa. Route to app, with method xsuaa
      When user calls the approuter-URL, then the logon screen is opened and user logs in and then user-logon-info will be available in the JWT token.
      See here for approuter-learning
      See here for an example with user-centric grant
      Cheers, Carlos

      Author's profile photo Rafael Zanetti
      Rafael Zanetti

      Thanks Carlos Roggan

      However, I am using the approuter but I need to communicate backend-backend, with this structure:

      USER -> approuter -> Node Backend 1 -> Node Backend 2 (in a different xsuaa)

      Is it possible to send the user id in this backend-backend communication?

       

      Thanks!

      Rafael Zanetti

       

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

      Hello Rafael Zanetti
      If the user logs in with App Router, then OpenID Connect is used for authentication. You will see the openid scope in the JWT token, along with some user data (like email, familyname, etc hope that this is what you need)
      This token is forwarded to your backend (if configured in the destination with forward=true)
      In your backend, you can take the token and use it in authorization header, when calling the backend2
      However, you say that your backend2 has different XSUAA (makes sense)
      In that case, the JWT will be rejected.
      BUT, what you can do: use Destination of type TokenExchange.
      In this case, you configure the destination with the clientID etc of backend2-xsuaa
      When calling backend2 via the tokenExchange, you send your existing JWT token to the destination, and you get a new JWT token, which is accepted by the backend2

      Here's a link to the official docu

      Hope this helps!
      Cheers,
      Carlos

      Author's profile photo Anil k
      Anil k

      Thank you.  I’m trying out a multi tenant application for CF with spring boot backend.

       

      My setup is  approuter –> springboot backend.  I’m facing    ”  An error occurred while attempting to decode the Jwt: Signed JWT rejected: Invalid signature”   error  when i try to subscribe my application from customer tenant .

       

      From the the documentation it is  said i need to  set

      SAP_JWT_TRUST_ACL:'[{“clientid”:”*”,”identityzone”:”sap-provisioning”}]’   , however from your blog post , looks like it is deprecated .  Can you please help me on how to resolve this.

       

       

       

      Author's profile photo Anil k
      Anil k

      After few trials and reading, I was able to resolve it by adding  below

      "$XSAPPNAME(application,sap-provisioning,tenant-onboarding)"
      
      Thanks, great post
      Author's profile photo Nena Raab
      Nena Raab

      Hi Carlos Roggan

      thanks for the blog. Please add a link to this TechEd self-study material:
      https://github.com/SAP-samples/teched2020-DEV263/tree/main/exercises/ex5_sap_jwt_trust_acl

      Best regards,

      Nena

      Author's profile photo Eik Sunke
      Eik Sunke

      Hi Carlos Roggan

      I am trying the app2app scenario in an HANA XSA installation and it does not seem to work.

      Do you know if this is supposed to work there as well? I have doubts because while looking at something like

      ["$XSAPPNAME(application,

      where "application" is the plan of the xsuaa service, this is not possible with the available plans of XSA. I tryed with default and space but get exceptions.

      Best regrads,
      Eik

      Author's profile photo Eik Sunke
      Eik Sunke

      I used the time over the weekend to think about this issue. And I was thinking that maybe the old way will work in XSA. I tried it and yes, the outdated parameter "SAP_JWT_TRUST_ACL" does work fine in a HANA XSA SPS4 environment.

      While looking deeper into the topic, I saw that I was using xssec in version 2.2. Looking at the changelog, starting from 3 the audience functionality comes in. When I use the latest version, the "SAP_JWT_TRUST_ACL" does not work any more. However, this brings me back to my original question: How can I make the setup work on XSA? The configuration above does not work even with the latest version of xssec.

      Author's profile photo Rossano Rosica
      Rossano Rosica

      Hi Carlos,

      many thanks for your great explained blog.

      In the npm documentation of xssec there is a section regarding the ACL.

      It says:

      • "However, there are some use cases, when a "foreign" token could be accepted although it was not intended for the current application. If you want to enable other applications calling your application backend directly, you can specify in your xs-security.json file an access control list (ACL) entry and declare which OAuth client from which Identity Zone may call your backend."

      It seems like we can still use the ACL with a configuration of the xs-security.json but there isn't a place with an "how to do it". Do you know how?

      Regards,
      Rossano

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

      Hi Rossano Rosica ,
      thanks for the feedback and thanks for carefully reading the docu and pointing out this section.
      You can ignore it, it will be removed soon.

      Kind Regards and keep carefully reading blogs and docus 😉

      Cheers,
      Carlos

      Author's profile photo Michael Smith
      Michael Smith

      Carlos Roggan , Thank you for this blog, but I am still stumped. 🙁  I am currently in the process of migrating a Neo UI5 app with a Java servlet dependency to Cloud Foundry.  In Neo, it was simple - deploy the servlet to Neo and create a destination with AppToAppSSO.  Then, any of our UI5 applications could access the servlet without any extra effort.  To say SAP Cloud Foundry has complicated things would be a huge understatement. 😉

      It appears the only way to access a servlet from UI5 in CF is by creating a standalone app router in my project, so I let BAS generate a brand new multi-target application, added a standalone app router module and a UI5 module.  By following some other blogs, I converted the java servlet to a maven project (I guess this was required??), created and bound its necessary services in the CF cockpit, made the necessary adjustments to the xs-app.json, the mta.yaml, and the destinations.json files in the multi-target app, and added a small Text control to the UI5 view (this UI5 app is completely bare-bones, by the way) to display the results of calling the servlet.  So, I got a 401 error on the servlet, looked through the log of my servlet and found the "Request could not be authenticated . . . jwt token with audience . . . is not issued" error, which led me to this blog.

      I have created a xs-security.json file in both the multi target app and the java servlet with the scope grant and so forth.  Unfortunately, I am still receiving the same 401 error.  Please provide guidance when you have a moment.  Attached are a screenshot of the error from the log, the xs-security file from the multi target app (mtatest) and the xs-security file from the java servlet (userinfo):

      401%20Unauthorized%20error

      401 Unauthorized error

      xs-security.json of mtatest:

      {
        "xsappname": "mtatest",
        "tenant-mode": "dedicated",
        "description": "Security profile of called application",
        "authorities":["$XSAPPNAME(application,userinfo).Display"],
        "scopes": [
          {
            "name": "uaa.user",
            "description": "UAA"
          }
        ],
        "role-templates": [
          {
            "name": "Token_Exchange",
            "description": "UAA",
            "scope-references": [
              "uaa.user"
            ]
          }
        ]
      }
      

      xs-security.json of userinfo:

      {
        "xsappname": "userinfo",
        "tenant-mode": "shared",
        "scopes": [
          {
            "name": "$XSAPPNAME.Display",
            "description": "display",
            "grant-as-authority-to-apps" : [ "$XSAPPNAME(application, mtatest)"]
          }
        ],
        "role-templates": [
          {
            "name": "Viewer",
            "description": "Required to view things in your solution",
            "scope-references"     : [
              "$XSAPPNAME.Display"
            ]
          }
        ]
      }
      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hello Michael Smith - thank you for the description, I have an idea what could be the issue.
      But I'd like to ask you to post this question in https://answers.sap.com because it is a big question and answer might be useful for many other users.
      You can just copy&paste your question there.
      OK?
      Thanks, and see you there.
      Kind Regards,
      Carlos

      Author's profile photo Michael Smith
      Michael Smith

      I have updated my existing question out there: https://answers.sap.com/questions/13567578/how-do-i-access-a-java-servlet-from-a-ui5-applicat.html