Skip to Content

In this blog post, I will briefly explain why there are ‘destinations’ in the SAP Cloud Platform. I will also show some hands-on examples of how to consume them.

Leveraging existing services and data sources is the core of each cloud-based business application out there. Connecting applications to those destinations is a very crucial element of your application and should be done the right way. SAP Cloud Platform recognized the importance of this element and offers with the destination service a scalable solution in the Cloud Foundry Environment.

To keep this post as simple as possible, I won’t dive into the SAP Cloud Connector or the SAP Cloud Platform Connectivity Service, which is a proxy service to redirect requests to on-premise systems (I recommend this blog post if you’re interested in this scenario).

Why do I need destinations

I think all of us agree that it would be easiest to hardcode the destination information into the codebase. But this (rookie) mistake has several issues, just to name a few:

  1. Re-usability

You would need to copy and paste the same information into several modules/apps when you want to re-use it (=> Antipattern)

  1. Standardized form

Closely related to the re-usability aspect, you need to come up with a standardized format (e.g. do you prefer the properties ‘protocol’, ‘hostname’, ‘port’ or would you rather combine them to ‘URL’?). It’s crucial to enforce this format across all destinations

  1. Sensitive information in the code base

Never ever (ever!) add sensitive information, such as passwords, to your code! I think I don’t need to explain this one

  1. Need to handle various authentication strategies

Authentication strategies vary access different destinations types and you need to implement each single one

  1. Integration with development tools

Dev tools (like code generators) have virtually no use if they cannot access supporting information

How does this translate to the “Cloud Foundry world”?

The previous section did not only show that information about the endpoints should be stored centrally, it also showed that there is a need for “business logic” to consume it.

In general, applications shouldn’t be hardwired to resources (like text files or database systems), but be rather coupled loosely to them. Backing services are the best practice to couple resources to applications running in Cloud Foundry environments. Therefore, SAP encapsulates the necessary business logic and the persistence layer for those tasks and makes them easy to consume as a backing service (Destination service).

Your application can access the destinations via a so called (Destination) service instance.

Using the destinations-service in the backend

There are three steps to enable a backend application to access destinations

  1. Enter the information for the destination

I recommend that you enter them on the subaccount level in the Cloud Foundry environment (it’s also possible to add them on the service instance level).

  1. Create a destinations service instance

This service is able to read and transmit the information you stored in the previous step it securely.

  1. Create an xsuaa service instance

This service issues a JWT token to authenticate to the destinations service instance.

Same as the previous step, just filter the marketplace for “trust” instead.

My colleague Matthieu Pelatan has described this process of retrieving the destination information very detailed in a mini-blog-series (Part 1, Part 2, Part 3). Therefore, I would like to keep this section rather short and simply list the necessary steps here:

  1. Read required the environment variables
  2. Use the retrieved values to request a JWT access token (for the destinations service instance) from the UAA
  3. Get the destination details from the destination service instance
  4. Request the resource behind your destination!

 

The official documentation contains nice code samples for Java and your terminal (curl), so instead, I thought I’d supply some code in JavaScript and Python.

Node.js [JavaScript]

This snippet uses the npm package request, which can be substituted with a module of your choice.

 

const request = require('request');
const cfenv = require('cfenv');

/*********************************************************************
 *************** Step 1: Read the environment variables ***************
 *********************************************************************/
const oServices = cfenv.getAppEnv().getServices();
const uaa_service = cfenv.getAppEnv().getService('uaa_service');
const dest_service = cfenv.getAppEnv().getService('destination_service');
const sUaaCredentials = dest_service.credentials.clientid + ':' + dest_service.credentials.clientsecret;

const sDestinationName = 'scp';
const sEndpoint = 'secure/';

/*********************************************************************
 **** Step 2: Request a JWT token to access the destination service ***
 *********************************************************************/
const post_options = {
    url: uaa_service.credentials.url + '/oauth/token',
    method: 'POST',
    headers: {
        'Authorization': 'Basic ' + Buffer.from(sUaaCredentials).toString('base64'),
        'Content-type': 'application/x-www-form-urlencoded'
    },
    form: {
        'client_id': dest_service.credentials.clientid,
        'grant_type': 'client_credentials'
    }
}

request(post_options, (err, res, data) => {
    if (res.statusCode === 200) {

        /*************************************************************
         *** Step 3: Search your destination in the destination service ***
         *************************************************************/
        const token = JSON.parse(data).access_token;
        const get_options = {
            url: dest_service.credentials.uri + '/destination-configuration/v1/destinations/' + sDestinationName,
            headers: {
                'Authorization': 'Bearer ' + token
            }
        }

        request(get_options, (err, res, data) => {

            /*********************************************************
             ********* Step 4: Access the destination securely *******
             *********************************************************/
            const oDestination = JSON.parse(data);
            const token = oDestination.authTokens[0];

            const options = {
                method: 'GET',
                url: oDestination.destinationConfiguration.URL + sEndpoint,
                headers: {
                    'Authorization': `${token.type} ${token.value}`
                }
            };

            request(options).on('data', (s) => {
                console.log(s.toString())
            });
        });
    }
});

Python

SAP recently announced that the Python runtime is officially supported now! So, I would like to take this as an occasion to include a python script here:

from cfenv import AppEnv
import requests
import base64


######################################################################
############### Step 1: Read the environment variables ###############
######################################################################

env = AppEnv()
uaa_service = env.get_service(name='uaa_service')
dest_service = env.get_service(name='destination_service')
sUaaCredentials = dest_service.credentials["clientid"] + ':' + dest_service.credentials["clientsecret"]
sDestinationName = 'scp'

######################################################################
#### Step 2: Request a JWT token to access the destination service ###
######################################################################

headers = {'Authorization': 'Basic '+base64.b64encode(sUaaCredentials), 'content-type': 'application/x-www-form-urlencoded'}
form = [('client_id', dest_service.credentials["clientid"] ), ('grant_type', 'client_credentials')]

r = requests.post(uaa_service.credentials["url"] + '/oauth/token', data=form, headers=headers)

######################################################################
####### Step 3: Search your destination in the destination service #######
######################################################################

token = r.json()["access_token"]
headers= { 'Authorization': 'Bearer ' + token }

r = requests.get(dest_service.credentials["uri"] + '/destination-configuration/v1/destinations/'+sDestinationName, headers=headers)

######################################################################
############### Step 4: Access the destination securely ###############
######################################################################

destination = r.json()
token = destination["authTokens"][0]
headers= { 'Authorization': token["type"] + ' ' + token["value"] }

r = requests.get(destination["destinationConfiguration"]["URL"] + 'secure/', headers=headers)
print(r.text)

 

Using the destination service in the frontend (= SAPUI5 web apps)

Apart from the reasons above, there is one additional reason why you want to have a destination service for web apps:

Same Origin Policy

I’m sure every web dev out there cursed this mechanism at least once in his/her life. For the “lucky” ones of you who didn’t have to deal with SOP before: It is a security mechanism of your browser that basically blocks all requests your client-side script sends to any web server, except the web server which delivered this script.

The most common workaround is sending a request to your web server, which proxies the request to the final destination. In the SAP Cloud Platform on the other side, you don’t need to bother at all! The runtime environment knows how to access the destination out-of-box.

You don’t need to request a JWT token from the XSUAA service instance and you don’t need to request the destination information from the destinations-service. You don’t even have to implement the authentication strategy. SAP Cloud Platform takes care of all that for you. 

Here’s what you have to do in the SAPUI5 application:

 

  1. Enter the information for the destination

I recommend that you enter them on the subaccount level in the Cloud Foundry environment (it’s also possible to add them on the service instance level).

 

 

  1. Make sure both services are bound to the app in the mta.yml file (SAP Web IDE should create those entries by default):
resources:
 - name: uaa_service
   parameters:
      path: ./xs-security.json
      service-plan: application
      service: xsuaa
   type: org.cloudfoundry.managed-service

 - name: destination_service
   parameters:
      service-plan: lite
      service: destination
   type: org.cloudfoundry.managed-service
  1. Add the route to the destination in the file xs-app.json. This descriptor file tells the web server how the requests need to be proxied to the respective destination:
"routes": [{
	  "source": "^/scp/(.*)",
	  "target": "/$1",
	  "destination": "scp"
}...
  1. Call endpoint from the application
$.get('/scp/secure').then((sMsg)=>{alert(sMsg)});

Conclusion

There are many good reasons why you should use destinations in the Cloud Foundry (and Neo) environment, such as preventing the use of anti-patterns, storing your credentials in a safe place (in general just to make your life easier). At first sight, the process of retrieving those destinations in your application might seem confusing. I hope this article could shed some light into this process and prove that’s it’s actually quite simple.

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply