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.
cancel
Showing results for 
Search instead for 
Did you mean: 
mariusobert
Developer Advocate
Developer Advocate
78,186
In this blog post, I will briefly explain why there are ‘destinations’ in SAP BTP, Cloud Foundry. 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 BTP 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 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).

Updates:

19th Nov 2020 - Refresh GIFs

29th Jan 2021: Rebranding to SAP BTP

9th Jan 2021: Add video tutorial to test destinations

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 codebase


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 can I make sure my destinations are set up correctly?


I recorded a short video tutorial that you can follow to make sure your destination is working as expected. Do you know your destination is already accessible? Then jump ahead to the next section.


Watch the video on YouTube



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, but 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 it 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)

Please note that you need to declare the modules "request" and "cfenv" to your app via a requirements.txt file before deploying the code.

 

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 webserver which delivered this script.

The most common workaround is sending a request to your web server, which proxies the request to the final destination. With the SAP Application Router, on the other side, you don’t need to bother at all! The router 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 Application Router 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. The SAP Business Application Studio should create those resources and bindings by default. Make sure both services are defined ("resources" section) and bound to the app ("requires" section) in the mta.yml file:



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, ABAP, and Kubernetes 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 on this process and prove that’s it’s actually quite simple.
121 Comments
0 Kudos
Marius I followed the following tutorial tutorial_link that shows how to migrate a NEO application to Cloud Foundry there says that I have to create a new project to be able to move the old project... I don't know if that's what you mean.
mariusobert
Developer Advocate
Developer Advocate
0 Kudos
I'd suggest that you use the wizard to create a new application from the scratch (as shown in the video) and then compare the manifest.json file of that application with yours. This is probably the easiest way to see what is missing.
0 Kudos

I saw the video and the git_link application repository nowhere is a manifest.json file, however, I created an SAPUI5 application using the wizard, and compared the manifes.json files, but both look so similar, the only difference between them is this:


 







migration application

new SAPUI5 application


 

 
martin_donadio
Product and Topic Expert
Product and Topic Expert
0 Kudos
Hi Marius,

I'm just curious about one thing.

In this example, you don't bind the NodeJS module to a connectivity service. Is this because your destination is not pointing to an On-Prem resource ? I mean, because your destination points to Internet proxy type?

Thanks for this great post !! Very valuable 🙂

Best regards,

Martin
mariusobert
Developer Advocate
Developer Advocate
Hi Martin,

correct. I don't require a binding to the connectivity service because I'm not accessing an on-prem destination (via principle propagation) here.
ruben_alvarez
Explorer
0 Kudos
Hi Marius,

I have created a ui5 app by yo command:

yo easy-ui5 project.

This app uses a destination, when I run it locally it works fine but when I display it in cf the request gives error 404.

The app is shown in HTML5 apps  subaccount and in space Foundry the following instances are created



























 

I don't know what I'm doing wrong.

I will waiting for your answer and thanks for help me

Thanks

File xs-app.json in webapp folder:

{

  "welcomeFile": "/index.html",

  "routes": [

    {

      "source": "^/sap/opu/odata/sap/(.*)$",

      "authenticationType": "none",

      "destination": "euroberry_s4h",

      "csrfProtection": false

    },

    {

      "source": "^/user-api/currentUser$",

      "target": "/currentUser",

      "service": "sap-approuter-userapi"

    },

    {

      "source": "^(.*)",

      "target": "$1",

      "authenticationType": "xsuaa",

      "service": "html5-apps-repo-rt"

    }

  ]

}




MTA.yml




ID: segcal

_schema-version: 3.2.0

version: 0.0.1

parameters:

  enable-parallel-deployments: true

modules:

  - name: webapp_deployer

    type: com.sap.application.content

    path: deployer

    requires:

      - name: segcal_html5_repo_host

        parameters:

          content-target: true

    build-parameters:

      build-result: resources

      requires:

        - name: uimodule

          artifacts:

            - dist/uimodule.zip

          target-path: resources/

  - name: segcaldestination-content

    type: com.sap.application.content

    build-parameters:

      no-source: true

    requires:

      - name: segcal_uaa

        parameters:

          service-key:

            name: segcal_uaa-key

      - name: segcal_html5_repo_host

        parameters:

          service-key:

            name: segcal_html5_repo_host-key

      - name: segcal_destination

        parameters:

          content-target: true

    parameters:

      content:

        instance:

          existing_destinations_policy: update

          destinations:

            - Name: segcal_html5_repo_host

              ServiceInstanceName: segcal_html5_repo_host

              ServiceKeyName: segcal_html5_repo_host-key

              sap.cloud.service: segcal.service

            - Name: segcal_uaa

              Authentication: OAuth2UserTokenExchange

              ServiceInstanceName: segcal_uaa

              ServiceKeyName: segcal_uaa-key

              sap.cloud.service: segcal.service

  - name: uimodule

    type: html5

    path: uimodule

    build-parameters:

      builder: custom

      commands:

        - npm run build:uimodule --prefix ..

      supported-platforms: []

resources:

  - name: segcal_destination

    type: org.cloudfoundry.managed-service

    parameters:

      service-plan: lite

      service: destination

      config:

        HTML5Runtime_enabled: true

        version: 1.0.0

  - name: segcal_html5_repo_host

    type: org.cloudfoundry.managed-service

    parameters:

      service-plan: app-host

      service: html5-apps-repo

      config:

        sizeLimit: 2

  - name: segcal_uaa

    type: org.cloudfoundry.managed-service

    parameters:

      path: ./xs-security.json

      service-plan: application

      service: xsuaa


mariusobert
Developer Advocate
Developer Advocate
0 Kudos
My guess is that the xs-app.json config is incorrect. It could mean that either of the routes is wrong. Please have a look at the log of the approuter to find more hints.
former_member731995
Discoverer
0 Kudos
Hi Marius,

I have created an UI5 app (not MTA) in BAS with the option to add the application to managed application router in a CF environment.

In BTP I have a destination to an OnPremise system:

This system doesn't have a gateway, but I just want to consume a REST web service in it:

http://****.entreprise.dom:1443/sap/ZFiori_WS/SO_Get

 

In my application, I add this code in xs-app.json:
    {
"source": "^/Fiori_WS/(.*)$",
"target": "/sap/ZFiori_WS/$1",
"authenticationType": "xsuaa",
"csrfProtection": false,
"destination": "Fiori_WS"
}

And I use the following code in the controller:
const cWs = "Fiori_WS/SO_Get";

_WsGetInit: function () {
var xhttp = new XMLHttpRequest();

xhttp.onreadystatechange = function () {
if (this.readyState == 4) {
MessageBox.information(this.responseText);
}
};

var vUrl = cWs + "?Action=GetInit";
xhttp.open("GET", vUrl, true);

xhttp.send();
},

My problem is if I test the application in CF (via HTML5 applications tab in my subaccount), it's ok, the app consume the web service.

In the launchpad service, I can see the app with the content provider and add it to my launchpad, but then I get a 404 error not found when the app try to consume the web service...

I don't see what is missing. It should be pretty simple.
In the BAS terminal I can access the web service, in CF too but not in launchpad service

If you have any idea, it will be helpful !

Thanks
mariusobert
Developer Advocate
Developer Advocate
0 Kudos
Hi Fabrice,

the setup you described sounds good to me. And given that it works via the HTML5 Apps tab, I don't think your coding causes this problem. I'd suggest opening a issue so that someone from the Launchpad team can have a look.

 
former_member731995
Discoverer
Thanks for your quick answer!
I will open an issue
0 Kudos
Hi Gini, can you please help to provide sample code for this? I still cannot figure this out. Thank you in advance.
gleiss
Advisor
Advisor
0 Kudos
Hi Marius,

is there also an (e.g. cloud foundry based) API to update destinations without using the actual destination service? Let's say, I would like to update some destinations of several subaccounts automatically from a pipeline, but without authenticating against the destination service?
Thanks and best regards,
Eric
mariusobert
Developer Advocate
Developer Advocate
0 Kudos
You'll always need to interact with the destination service if you want to add or edit destination entires. But there is a way to delegate these tasks to the CF deploy service. If you do this, the destination definition can be encoded in the mta.yaml file. Follow this link to a GitHub repo to see how it can be used.
0 Kudos
Hi Marius,

I checked everything and every way before asking you believe me, but i have still a problem about consuming data from my odata service, let me explain my case and i would be appreciated if you give a hand.

This is the data service which I want to consume in a UI5 application.

https://xxx.cfapps.eu10.hana.ondemand.com/api/outbound/odata/v1/com.emea.gtt.app.gttft1.gttft1Servic...

When i give the technical username and password, i can consume it in incognito session in a browser without any restriction normally.

Here is my destination definition in the cloud.

URL was entered with just host name : https://xxx.cfapps.eu10.hana.ondemand.com


 

Here xs-app.json configuration.


I also tried to set the target as just '$1' and it didn't work.

Here mta.yaml resources.


And the app is running on the cloud in a multi-environment as below.



 

But i always receive 404 when i try to retrieve metadata.xml.

What is wrong in my set up? What should be the authType in xs-app.json when i define the route?

I also defined the same destination inside destination service which is bind to the app.but it didn't work again.

 

Thanks in advance for all support and guidance.
maty1989
Discoverer
0 Kudos
Hi Yalcin. I have the same problem and same configuration.

It works for me on local test, but when i deploy, i recive not found.

Could you find the solution?

Thanks
0 Kudos
Hi Matias,

Unfortunately, I couldn't find any solution yet, as i said, it is quite weird. My endpoint works everywhere with user auth. And in the NEO environment. But I am not able to consume it in CloudFoundry.
0 Kudos
HI mariusobert

Could you please share blog on "

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


"

with XSUAA auth in API and consuming in SAPUI5
kevindass
Participant
0 Kudos
mariusobert Firstly amazing blog and appreciate all of your contents.

I have being working on SAP-samples /multi-cloud-html5-apps-samples for a while now and exploring destination service . It has given me a better understanding with apps in different flavors on BTP-CF.


We all start with Northwind Odata and all goes well. However I did explore REST API example https://reqres.in/ and it works well while running on SAP BAS. However when deployed on CF and configured on Launchpad Service it has issues connecting to destination.


I did a lot of reading on SAP blogs and many samples. However, didn't get a solve yet/ I am sure I am missing something w.r.t destination service.


I have posted question as well https://answers.sap.com/questions/13761787/sap-btp-launchpad-services-integrate-the-rest-api.html


I would highly appreciate if you could assist me here.


Regards,
Kevin Dass


ehuba
Explorer
0 Kudos
Hello,

I used your javascript code snippet in a CAP app to send a request to another CAP app via a destination. This destination uses OAuth2UserTokenExchange authentication.

Everything works well until step 4.

Here, when I get a value in the oDestination variable, the token looks like this:

"authTokens":[{"type":"","value":"","error":"Retrieval of OAuthToken failed due to: Unable to fetch refresh token from the specified token service URL. Response was: Insufficient scope for this resource","expires_in":"0"}]



Can you please give some hint or solution what is the missing piece here?

I tried to configure both app's xs-security.json based on Add Authentication and Functional Authorization Checks to Your Application, but nothing helped.


best regards,

Huba Erdős
ipravir
Active Contributor
0 Kudos

Hi @mariusobert ,

Thanks for sharing.

I’ve developed a compact Python program that provides data in JSON format.

After deploying it to Cloud Foundry, I confirmed that it works flawlessly when accessed via the deployed link. Additionally, I tested the same link as a destination, and it returned a 200 OK status code.

However, when I attempt to integrate this functionality into my Fiori application, I encounter an error code.

Please find below link with all details:

https://community.sap.com/t5/technology-q-a/how-to-access-destination-from-bas-which-created-using-p...

I would appreciate your input on whether there are any maintenance tasks to address or additional actions required.

Regards,

Praveer.

 

manishmatte1753
Explorer

Hello mariusobert,

Thanks for the amazing blog.