SAP BTP Security: How to use mTLS with Destinations

When defining a destination in SAP BTP, we have the option of using mTLS.
But how to provide the certificate?
This blog post describes in a detailed step-by-step tutorial how to configure a destination with a certificate and how to get a hold of it.
Used technologies: SAP BTP Cloud Foundry environment, Destination Service, XSUAA, Node.js

Quicklinks:
Quick Guide
Sample Code

Content

0. Introduction
1. Sample Application
2. Certificate
3. Destination
4. Run
5. Cleanup
6. Optional: Certificate Configuration
Appendix 1: Sample Code for Applications
Appendix 2: Sample Code for Destination Configuration

0.1. Introduction

Let’s have a look at a common scenario:

We want to call the URL of a service endpoint of an application.
As usual, the endpoint is protected with OAuth.
This means, we need to send a valid JWT token when doing a REST call (programmatically or with REST client).
How to get hold of a JWT token?
A client calls the (OAuth-) Authorization Server, which issues the token.
In Cloud Foundry, it is the XSUAA.

A client needs to be registered, so it gets credentials in form of a user (clientid) and a password (clientsecret).
So it gets enabled to call the Authorization server.
In Cloud Foundry, we get a registered OAuth client by creating an instance of XSUAA service.
The service instance reveals the credentials when it is bound to an application.
So the application code can programmatically access the credentials from environment variable.
Alternatively, a service key can be created and it can be used to view the credentials.The credentials contain the clientid, clientsecret and URL of the Authorization server.
Afterwards, we can use the clientid/secret (just like user/pwd) to call that URL and fetch a JWT token.

Credential Rotation:

Using clientid/secret is like using user/password.
And we’ve learned that a password should be changed, for security reasons.
With other words: password should be rotated.
In above described scenario, we need to delete the instance of XSUAA and recreate it, if we want a new clientsecret.
Reason:
The default credential-type is instance-secret.
Means: one secret per instance.

When creating an instance of XSUAA, we can specify the credential-type.
Another option could be: binding-secret.
This means, that a new secret is given on each new binding.
So to change the secret, we only need to unbind  and rebind the XSUAA-instance from our app.
For more info, refer to the SAP Help Portal.

mTLS

And there’s a third option: x509.
If this credential type is specified, no secret will be given in the binding.
Instead, a certificate is provided.
Changing the certificate is similar as before: unbind/rebind will lead to generation of new certificate.
In addition, a certificate can be configured to expire, independently of binding.
As such, this third option is the most interesting one, with respect to security.

Summarizing

We want to call XSUAA to fetch a JWT token.
We need to authenticate.
We can authenticate with certificate instead of password (secret).
This is called called “client certificate” (we are clients, XSUAA is server).
This is also called “mutual TLS”:
The server proves its honesty with certificate, and the client does the same (mutual).
For security reasons, the client certificate should be renewed (rotated).
This is done by unbinding, then rebinding the XSUAA instance to our app.

Destination

Now the destination service comes into the picture.
Typically, we use the destination service to externalize URLs, for better maintenance, etc.
In addition, the destination service offers the convenience of taking care of fetching a JWT token for the target app (backend app).
That’s good.

Now, when fetching the JWT token for us, the destination service has the same choice: use client secret or client certificate.
We don’t need to worry about it, as we don’t even notice how the destination service will do it.
But we need to configure the destination accordingly.

Tutorial

Below tutorial explains all required steps in detail.
As sample scenario, we’re using app-2-app, with client-credentials flow and a destination of type OAuth2ClientCredentials.
Both apps are written in Node.js and kept as minimalistic as possible.
The focus of the tutorial is how to configure the destination with the certificate.

0.2. Prerequisites

To follow this tutorial, we need

  • Access to SAP Business Technology Platform (SAP BTP) and permission to create instances and to deploy applications.
  • Basic Node.js skills.
  • Some basic understanding of OAuth.
  • The tutorial is using the Cloud Foundry command line client, but all tasks can be done in the cockpit as well
  • The command line tool openssl is being used briefly below. I’ve downloaded the windows version of it, that works fine, no prob.

0.3. Preparation

To prepare for deploying the applications, we create the sample project on file system and create the 2 required service instances.

0.3.1. Create Project Structure

To follow this tutorial, we create a project mtlsapp which contains our 2 applications:

C:\mtlsapp
backend
package.json
server.js
certificate
certificate.cer
mtlsXsuaa_client_certificate.pem
privateKey.txt
frontend
package.json
server.js
manifest.yml
xs-security.json

Or see this screenshot:

The file content can be copied from the appendix.
The files in the certificate folder are empty, as they will be filled during the course of the tutorial.

0.3.2. Create instance of XSUAA

The instance of XSUAA is used in 2 aspects:
* it is used by the backend app to to configure the protection of the endpoint.
* it is used by the destination service to fetch a token for calling the backend app

When creating the instance, we configure the credential type:
xs-security.json:

. . . "oauth2-configuration": { "credential-types": ["x509"]

This means, our instance supports mTLS only.
Each binding (we need only one) will get the client certificate.
Note:
Alternatively, we could specify all 3 credential types.
Afterwards, when binding to an app, additional parameters could be specified to choose the desired credential type for the binding.

On command line, we navigate to folder c:\mtlsapp and execute the following command:
cf cs xsuaa application mtlsXsuaa -c xs-security.json

0.3.3. Create instance of Destination Service

We bind our application to the instance of Destination Service.
As such, in our code, we can call the destination service to obtain the target URL and the JWT token.
We execute the following command:
cf cs destination lite mtlsDestination

1. Create Application

In our sample scenario, we have to little applications.
The frontend app calls the protected endpoint of the backend app.
To do so, it uses the destination service.
That’s all.

1.1. Backend Application

Our backend app is a simple server app that exposes a REST endpoint which is protected with OAuth.
It is called backend app to make clear that it is meant to be called by others.

const UAA_CREDENTIALS = xsenv.getServices({myXsuaa: {tag: 'xsuaa'}}).myXsuaa
. . .
const JWTStrategy = xssec.JWTStrategy
passport.use('JWT', new JWTStrategy(UAA_CREDENTIALS))
. . . app.get('/endpoint', passport.authenticate('JWT', {session: false}), (req, res) => {
. . .

The app uses passport middleware to protect the endpoint.
The passport middleware is configured with the JWTStrategie implemented by @sap/xssec library.
The JWTStrategy is configured with the binding to our instance of XSUAA.

We remember: we’ve configured this instance with x509….
But we can see that this has absolutely no effect on our code.

1.2. Frontend Application

The frontend application has no frontend.
It is just named frontend application to make clear that
– this is the app which is accessed by the end user
– this is the app which calls the backend app.

To access the frontend app, we just invoke an endpoint with a browser.
The endpoint itself is not protected, to make it easier.

The implementation of the app is simple and straightforward:

–  Access the binding of destination service instance
The destination service offers a REST API which itself is protected with OAuth.
So we need to fetch a JWT token in order to access the Destination Service.
To fetch a token, we read the binding information of Destination Service.
From binding, we get the 3 typical properties required to fetch a JWT token (url, clientid, clientsecret)
Once we have the destinationservice-JWT token we can use it to call the REST API of destination service.

app.get('/app', async (req, res) => { // call destination service const destJwtToken = await _fetchTokenForDestinationService(DESTINATION_CREDENTIALS) const destination = await _callDestinationService('destination_to_mtlsapp', destJwtToken)

To call the REST API of destination service, we need the URL of it.
We use the “find” API of Destination Service. It works as follows:
We ask it to “find” a destination configuration by a “name” that we tell.
If successful, we get the found destination information in the response.

const destServiceUrl = `${DESTINATION_CREDENTIALS.uri}/destination-configuration/v1/destinations/${destinationName}`
const options = { headers: { Authorization: 'Bearer ' + destinationSrvJwtToken }
}
const response = await fetch(destServiceUrl, options)

And what do we do with it?
Why did we call it at all?
Remember:
We use the destination service as convenience for calling a remote service.
We define a destination configuration (next chapter) and we specify the URL of the target service.
This is good practice, to avoid hard-coding the URL in our code (URL can change, e.g. when moving from dev-landscape to qual and prod)
But the best advantage: the destination service also takes care of fetching an OAuth access token for the target service.
In the configuration we have to provide the required info, and the destination service can handle even complex OAuth flows.
In our case, it will handle the OAuth-client-credentials flow which we configured to use client-certificate (instead of clientsecret).

const backendUrl = destination.destinationConfiguration.URL + '/endpoint'
const backendJwtToken = destination.authTokens[0].value
const response = await _callBackend(backendUrl, backendJwtToken)

Summary:
After calling the destination service, it will return our destination configuration AND a JWT token which we can use to call our target service.

Remember: the target service is our (protected) Backend Application.
So we can go ahead:
From the returned destination object, we extract the target URL and the JWT token.
Then we can use it to call the Backend Application.
This call should be successful.
Finally, our frontend app will print some useless information to the browser page.

1.3. Deploy

Our deployment descriptor contains both apps, so we need only one deploy step

manifest.yml

applications: - name: backendmtls path: backend routes: - route: backendmtls.cfapps.us10.hana.ondemand.com services: - mtlsXsuaa - name: frontendmtls path: frontend routes: - route: frontendmtls.cfapps.us10.hana.ondemand.com services: - mtlsDestination

We can see that the backend app is bound to the instance of XSUAA, which is used for protection.
The frontend app doesn’t need to be bound to XSUAA, because
– it is not protected
– it doesn’t fetch a JWT token manually from XSUAA.
Instead, the frontend app is bound to the instance of destination service, which will fetch the JWT token for us.
However, the destination service will need to know how to call XSUAA to fetch a JWT token.
How does the destination service know about the XSUAA?
Well, we’ll have to specify the required info in the destination configuration.

On command line, we navigate to folder c:\mtlsapp and execute the following command:
cf push

After deploy, we cannot open the app, as the destination configuration is missing.
But we can prepare the necessary data for the destination.
To access the binding information, we run
cf env backendmtls

We can see that the app is bound to the instance of XSUAA and in the binding info we can see the credentials node.

These credentials are needed, if we want to access the features of the service instance.
We use the credentials of XSUAA service instance, when we want to fetch a JWT token.
This token is suitable for calling the application which is protected with this XSUAA instance.
Typically, we need 3 properties of this credentials section:

clientid
clientsecret
url

However, when looking closely at our credentials section in the env, we don’t see the clientsecret.
Don’t worry, it is expected.
We see the credentials-type is x509, which means that we don’t get user/password credentials.
Instead we get a certificate and a key.
With certificate/key we can authenticate against the XSUAA server and fetch the JWT token.

Important to note:
When using the certificate to fetch a JWT token, we must use the certificate URL, not the “normal” URL.
The relevant property is the certurl.
So the final properties to remember are:

clientid
certurl
certificate
key

2. Certificate

Now, how to do that: use certificate/key to fetch a JWT token?
First of all, we use the destination service, to fetch a JWT token.
Second: it is still a valid question:
What to do with certificate information which we’ve seen in the binding?

This chapter will help simple-minded users (like me) to get a slow insight into this cryptic certificate world.

2.1. Copy key from env to file

We open the empty file c:\mtlsapp\certificates\privateKey.txt with a very simple editor (e.g. Notepad or VS Code text editor).
We copy the value of the key property from command line and paste it into the file.
It looks like this:

It is one long line full with silly characters.
Silly characters?
Yes, but not all.
There’s the BEGIN block and also an END block. These are simple to understand and are extremely important.
They say that anything begins and ends, and they say what it contains in between.
Ok.
And they must be separated from the rest by a line feed. That’s why we see the \n.
In the binding, everything comes in one long line, the line feeds are indicated by \n.
To make everything work, we need to manually adjust the content of the file:
Instead of \n we need real line feeds.

2.2. Adjust the private key

OK, the adjustments typically are done by machines or tools or libraries or (Linux) commands.
Today we do it manually, it makes things more clear.
Apologies.
We need to do just 2 adjustments:

2.2.1. Separate BEGIN and END blocks

Let’s add line feeds after the BEGIN block and before the END block, including hyphens, such that afterwards we have 3 lines:

2.2.2. Remove \n

We need to remove all the \n from the file content.
These \n are formatting information, have nothing to do with the key.
The reason why we have them:
The spec requires that each line contains exactly 64 characters (except last line).
So the \n in the text indicate the line break.
To remove those characters, we can do Ctrl + H in the editor and replace the \n with emtpy string.
Looks better:

Note:
Linux users might wish to use this command:
awk ‘{gsub(/\\n/,”\n”)}1’ privateKey.txt | tee privateKey_new.txt

Once done, we can save and close the file.

2.3. Copy the certificate to file

Now we repeat the same procedure with the certificate.
We open the empty file c:\mtlsapp\certificates\certificate.cer with a very simple editor and copy the value of the certificate property into it.
This time, the content is longer.
The reason is that it actually contains a chain of certificates:
The actual certificate, the intermediate certificate and the root certificate.
All separated by BEGIN and END sections and the line breaks indicated with \n.

2.4. Adjust the certificate

Again we need to adjust the file content, this time we have more blocks.

2.4.1. Separate BEGIN and END blocks

After searching for all BEGIN blocks, we can see we have 3 certificate blocks:

2.4.2. Remove \n

Again, we need to delete all occurrences of \n.

2.5. Check the order of certificates

What order?
A certificate has to be issued (signed) by somebody.
This somebody has to be trustworthy, otherwise nobody would trust the certificate.
To demonstrate that somebody is trustworthy, he shows a certificate himself.
Now what about this (intermediate) certificate? Is it trustworthy?
Yes, if it is as well signed by trustworthy authority.
This chain of trusting/signing certificates is called chain.
The last certificate is the root, it is trusted because it is a commonly accepted authority.

The order of the certificates in the chain is relevant.
The root certificate must come at the end.

How can we see that the last crowd of silly characters is a root certificate?
We can decode it and read the info in the header.

How to decode?
We can use any certificate decoder tool in the internet.
So we could copy the last certificate, including dashes and BEGIN and END and dashes, into clipboard.
Then paste into a certificate decoder tool, e.g. SSL Certificate Decoder  at sslchecker.dom/certdecoder
Note that it is not safe to upload sensible content in the internet.
So this is not a recommendation, it is just an example how easy it can be verified.

What is the alternative?
Alternatively, and obviously the better approach:
Use the command line tool openssl.

How to use it?
We copy and paste the bottom certificate (only) from file certificate.cer into a separate file bottomCert.cer.
Then run the command in the same folder:
openssl x509 -in bottomCert.cer -text -noout

The result shows that the issuer is the “Root CA”
Note:
“CA” stands for “certificate authority”

So after this check, we’re safe to go: the chain has correct order.

2.6. Compose .pem file

What now?
We need both, the private key and the certificate chain in one file.

Why?
We want to upload the bundled certificate-and_private-key into the destination dashboard.
This is necessary for configuring a destination with client certificate.
Such bundles can have different formats, and some of them are supported by the destination dashboard. See documentation: OAuth with X.509 Client Certificates.
The easiest way to go is a .pem file.

Easy?
Really.
As easy, as anything in the cryptography world?
That sounds tricky.
As easy as copy&paste.
In fact, we only need to do the following:

We have private key and certificate chain in proper (text) format.
We have an empty file with file extension .pem in our certificate folder.
I’ve called the pem file mtlsXsuaa_client_certificate.pem, because it contains the credentials that a client needs to call the instance of XSUAA, which is called “mtlsXsuaa” in our example.
So we just copy the content of both the key and chain files into the empty .pem file.
With other words: we’re merging the private key file and the certificate file.

That’s it?
Don’t forget to save.

Only one note:
Again, the order is relevant.
The private key block must be at the beginning of the file.
Afterwards the certificate chain in the existing correct order, ending with root.

In fact, that has been simple.

Note about file extensions:
All the files that we’re dealing with, are just text files, the file extensions are not really relevant.

What is the meaning of pem?
pem stands  originally for “Privacy Enhanced Mail”.
It describes a format.
It is in fact the most common format for X.509 certificates etc.
A .pem file is a text file containing one or more items in Base64 ASCII encoding, each with plain-text headers and footers
(e.g. —–BEGIN CERTIFICATE—– and —–END CERTIFICATE—–)
PEM is a container format for digital certificates and keys.
Finally, it is a file extension of a file that contains a bunch of certificate files.

So today, we use the .pem file as container that can be uploaded to destination dashboard.
The container is identified by its name and it contains the access certificate for connecting to XSUAA.
It is required for the destination service to fetch a JWT token.

3. Destination

Now that we’ve reformatted the certificate data that was contained in the binding of our backend application, we can go ahead and create a destination.
We want to specify our destination to use mTLS.
As such, before creating the destination, we need to upload our client-certificate to the dashboard.

3.1. Upload Certificate

Our destination will require that certificate, so let’s first upload it.
As mentioned, we upload a container file which contains the certificate and corresponding private key.

After login to our subaccount, we navigate to the destination dashboard:
Subaccount -> Connectivity -> Destinations
We press the “Certificates” button.
In the dialog, press “Upload Certificate”.
We browse to our pem file at C:\mtlsapp\certificate\mtlsXsuaa_client_certificate.pem
After short time, we get a green success message

We press “Cancel” to close the dialog.
Now that the destination dashboard has stored our certificate container file, we can go ahead and use it in a new destination.

Note:
The destination dashboard stores the certificate in encoded form.

3.2. Create Destination Configuration

We can now press “New Destination” and enter the details as follows:

Name
The name must match the name that is hard-coded in our frontend application.
In my example: “destination_to_mtlsapp”

Type
Here we choose “HTTP”

Description
For instance: “Destination with certificate pointing to mtlsapp”

URL
Here we enter the base URL of our backend application, without endpoint.
In the code, we need to append the endpoint (path).
In my example:
https://backendmtls.cfapps.us10.hana.ondemand.com

Proxy Type
“Internet”

Authentication
“OAuth2ClientCredentials”

Use mTLS for token retrieval
Yes, enabled.
If the checkbox is not editable, enter the Token Service URL first.
After enabling the checkbox, the fields are dynamically changed, as we need to specify the keystore in case of mTLS.

Client ID
The value of the property “clientid” from the binding of backend app, see step 1.5
In my example:
“sb-mtlsxsuaa!t81786”

Token Service Key Store Location
Here we use the dropdown to select the name of the certificate which we’ve uploaded in the previous step.
In my example: “mtlsXsuaa_client_certificate.pem”

Token Service Key Store Password
We leave it empty, as the pem file doesn’t require a password (required e.g. in case of password-protected jks keystore files, or p12 or pfx files)

Token Service URL Type
We leave the default “Dedicated”.
The other one is used in case of multitenant applications.

Token Service URL
The value from the “certurl” property in step 1.5.
Don’t forget to append the token endpoint segments (/oauth/token).
In my example:
https://mysubdomain.authentication.cert.us10.hana.ondemand.com/oauth/token

Token Service User
Empty

Token Service Password
Empty

That’s it.
Don’t forget to press “Save”
In my example, it looks like this:

Note:
Alternatively, the destination configuration can be imported from the appendix. and adjusted after import.

Now we’re done with configuration settings and we’re ready to run our application scenario.

4. Run

To run our scenario, we open the one and only endpoint of our frontend application.
In my example:
https://frontendmtls.cfapps.us10.hana.ondemand.com/app

As a result, we see the decoded content of the JWT token that was used to call the backend app.

This means, that we’ve properly configured the destination configuration, which in fact has been able to fetch a JWT token.
Which implies that all the effort we’ve had with the certificate has been properly done.

What about the highlighted text in the screenshot?
In the screenshot, I’ve marked a special new claim: the cnf.
cnf stands for “confirmation” and is used as “proof of possession”.
Means:
When we requested the JWT token, we’ve sent a client certificate for authentication.
And now this token contains an information about that certificate.
With other words: the JWT is bound to a certificate.
This needs to be confirmed.
To do so, there are several methods.
One method is called x5t#S256 (as printed in the screenshot)

What does that mean?
To bind a JWT to a certificate, it is obviously not desired to copy the whole certificate into the cnf claim.
Instead, the hash of the certificate is used.
Such hash is also called thumbprint or fingerprint or digest.
This thumbprint of the x509 certificate (-> x5t) is created with “SHA-256” algorithm (-> S256)
That’s it about the name of the confirmation method.

And the other text?
The value of this x5t… property is the thumbprint of the client certificate.
The receiver of the token has to validate the token and it will use the thumbprint found in the cnf claim.

5. Cleanup

The destination configuration can be deleted manually in the cockpit.
Artifacts to be deleted on command line:

cf login -a https://api.cf.us10.hana.ondemand.com
cf d backendmtls -r -f
cf d frontendmtls -r -f

cf ds mtlsXsuaa -f
cf ds mtlsDestination -f

6. Optional: Certificate Configuration

All this tutorial is confusing enough and no further info is required, I guess.
Nevertheless, I cannot close without elaborating on a comment I wrote above:
Certificates are secure because they can be configured to expire.

How to configure?
First let’s find out the current validity:
openssl x509 -in certificate.cer -text -noout

Above command will print the info of the first certificate in the chain.
Result:

We can see that the validity is 7 days.
This is the default setting of XSUAA which generates the certificate for us.
Now we want to generate a new client certificate, and this time we want to control some settings.
As we know, we can force generation of new certificate (rotate) by unbinding and rebinding the service instance.
When rebinding, we specify binding parameters in a separate file.
As such, we create a file config-binding.json in the root folder of our project.
The content:

{ "credential-type": "x509", "x509": { "key-length": 8192, "validity": 1, "validity-type": "DAYS" }
}

With this config, we’re setting the validity to 1 day, which is the minimum.
And we’re specifying that we want the private key to be generated with a length of 8192 bytes, which is the maximum.
See SAP Help Portal for reference.

Now we can go ahead and unbind the service instance from our app:
cf us backendmtls mtlsXsuaa

The we rebind it with the new config params file:
cf bs backendmtls mtlsXsuaa -c config-binding.json

Afterwards we need to restage our app, such that the env gets written
cf restage backendmtls

After some time, we can view the env
cf env backendmtls

First thing we notice is: the private key is much longer
Second, we need to find out the validity. As such, we copy the certificate value from the env into the certificate.cer file, adapt it as described above and execute the following command again:
openssl x509 -in certificate.cer -text -noout

The result shows that the certificate is now valid just 1 day:

Conclusion:
We’ve learned how to configure the generation of client certificate.

Summary

In the present tutorial, we’ve learned how to configure a destination for using mTLS.
First of all, we’ve learned how to configure the instance of XSUAA with credential type.
We’ve covered some basics about credentials rotations and certificates.
We’ve learned about the format of certificates and about uploading certificate container to the destination dashboard.

Quick Guide

When creating an instance of XSUAA, set the credential type to x509:
“oauth2-configuration”: { “credential-types”: [“x509”] }
Copy certificate and private key from app env into files.
Modify the text, such that \n are removed and replaced by real line feeds.
Check the order of certificate chain: root certificate must be located at the bottom.
Merge private key (first) and certificate chain (bottom) into one file with extension .pem
Upload this file to destination dashboard.
Create destination using this file, use certurl.

Links

SAP Help Portal
Reference for xs.security.json parameters.
Parameters for x509 in binding.

Passport homepage and download.
Node.js package xssec.
Manual for openssl and download the windows version.

Understanding of OAuth for dummies like me.

Appendix 1: Sample Code for Applications

xs-security.json

{ "xsappname": "mtlsxsuaa", "tenant-mode": "dedicated", "oauth2-configuration": { "credential-types": ["x509"] } }

manifest.yml

---
applications: - name: backendmtls path: backend memory: 64M routes: - route: backendmtls.cfapps.us10.hana.ondemand.com services: - mtlsXsuaa - name: frontendmtls path: frontend memory: 64M routes: - route: frontendmtls.cfapps.us10.hana.ondemand.com services: - mtlsDestination

backend app

package.json

{ "dependencies": { "@sap/xsenv": "latest", "@sap/xssec": "latest", "express": "^4.17.1", "passport": "^0.4.0" }
}

server.js

const xsenv = require('@sap/xsenv')
const UAA_CREDENTIALS = xsenv.getServices({myXsuaa: {tag: 'xsuaa'}}).myXsuaa const express = require('express')
const app = express();
const xssec = require('@sap/xssec')
const passport = require('passport')
const JWTStrategy = xssec.JWTStrategy
passport.use('JWT', new JWTStrategy(UAA_CREDENTIALS))
app.use(passport.initialize()) app.listen(process.env.PORT) app.get('/endpoint', passport.authenticate('JWT', {session: false}), (req, res) => { res.send(`Backend app successfully called.`)
})

frontend app

package.json

{ "dependencies": { "@sap/xsenv": "latest", "@sap/xssec": "^3.2.13", "express": "^4.17.1", "node-fetch": "2.6.2", "passport": "^0.4.0" }
}

server.js

const xsenv = require('@sap/xsenv')
const DESTINATION_CREDENTIALS = xsenv.getServices({myDestination: {tag: 'destination'}}).myDestination const fetch = require('node-fetch')
const xssec = require('@sap/xssec')
const express = require('express')
const app = express(); // start server
app.listen(process.env.PORT) // app entry endpoint
app.get('/app', async (req, res) => { // call destination service const destJwtToken = await _fetchTokenForDestinationService(DESTINATION_CREDENTIALS) const destination = await _callDestinationService('destination_to_mtlsapp', destJwtToken) // call backend app const backendUrl = destination.destinationConfiguration.URL + '/endpoint' const backendJwtToken = destination.authTokens[0].value const response = await _callBackend(backendUrl, backendJwtToken) // decode the JWT token const backendJwtDecoded = new xssec.TokenInfo(backendJwtToken).getPayload() res.send(`Frontend app called. Response from Backend: <p>${response}</p> The JWT token used for backend: <p>${JSON.stringify(backendJwtDecoded)}</p>`)
}) /* HELPER */ async function _fetchTokenForDestinationService (uaa){ return new Promise ((resolve, reject) => { xssec.requests.requestClientCredentialsToken(null, uaa, null, null, (error, token)=>{ resolve(token) }) }) } async function _callDestinationService(destinationName, jwtToken){ const destServiceUrl = `${DESTINATION_CREDENTIALS.uri}/destination-configuration/v1/destinations/${destinationName}` const options = { headers: { Authorization: 'Bearer ' + jwtToken } } const response = await fetch(destServiceUrl, options) const responseJson = await response.json() return responseJson
} async function _callBackend (url, jwtToken){ const options = { headers: { Authorization : `Bearer ${jwtToken}` } } const response = await fetch(url, options) const responseText = await response.text() return responseText
}

Appendix 2: Sample Code for Destination Configuration

destination_to_mtlsapp

Description=Destination with certificate pointing to mtlsapp
Type=HTTP
clientId=sb-mtlsxsuaa\!t81786
Authentication=OAuth2ClientCredentials
Name=destination_to_mtlsapp
tokenServiceURL=https\://frontendsubdomain.authentication.cert.us10.hana.ondemand.com/oauth/token
ProxyType=Internet
URL=https\://backendmtls.cfapps.us10.hana.ondemand.com
tokenServiceURLType=Dedicated
tokenService.KeyStoreLocation=mtlsXsuaa_client_certificate.pem