Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
Showing results for 
Search instead for 
Did you mean: 

SAP Cloud Platform Security:


is obsolete
and doesn’t work anymore

since May 2020 (roughly)

Quick Guide
Sample Code


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.


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


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:

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



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
"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
- name: clientapp
memory: 128M
- nodejs_buildpack
- 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

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???


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
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:
- name: clientapp
memory: 128M
- nodejs_buildpack
- xsuaaforclient

Before we push the app, we should delete the existing app (or delete the binding)
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

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

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

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?
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


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


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

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


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

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
It is easy

With other words:

provider app grants "provider"-scope to client
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

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:


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:


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

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

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

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",

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

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.


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

Secure communication from one app to second app

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"

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


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

How to add custom properties to JWT
View content of jwt token: -> Debugger
More spec
Even more spec

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

TechEd self-study material

Node client modules at npm

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

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.

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.

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

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

Authorized party - the party to which the ID Token was issued

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.

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.

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.

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.

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

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

"zid":"1234abcd-bdb9-42dd-a563-1234abcd 76bc",

Appendix 2: All Sample Project Files

For your convenience, see screenshot for overview about project structure

App 1: API Provider App

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

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

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);
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);

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, () => {})

- name: providerapp
memory: 128M
- nodejs_buildpack
- xsuaaforprovider
DEBUG: xssec:*

App 2: Client App

"xsappname" : "xsappforclient",

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

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){
res.status(202).send('Successfully called remote endpoint.');
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: '',
path: '/protected',
method: 'GET',
headers: {
Authorization: 'Bearer ' + jwtToken
const req = https.request(options, (res) => {
const status = res.statusCode
if (status !== 200 && status !== 201) {
return reject(new Error(`Failed to call endpoint. Error: ${status} - ${res.statusMessage}`))
res.on('data', () => {

req.on('error', (error) => {
return reject({error: error})

.catch((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 => {
let response = ''
res.on('data', chunk => {
response += chunk
res.on('end', () => {
try {
const jwtToken = JSON.parse(response).access_token
} 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, ()=>{})

- name: clientapp
memory: 128M
- nodejs_buildpack
- xsuaaforclient