Skip to Content
Technical Articles
Author's profile photo Nicolai Schoenteich

SAP Tech Bytes: Consume Data Using Destinations with an Approuter – Cloud Foundry Basics #3

This SAP Tech Byte is about how to use the Destination Service on Cloud Foundry and consume destinations using a node.js based approuter.

The source code for this blog post can be found on https://github.com/SAP-samples/sap-tech-bytes/tree/cloud-foundry-basics/post3.

Building on top of the previous blog post of this “Cloud Foundry Basics” series, where we learned how to serve a web page using a standalone approuter, we will use this approuter to consume data through a destination instance of the Destination Service on the SAP Business Technology Platform, Cloud Foundry environment today.

 

Current State of the Application

This is what the application currently looks like:

It is a node.js based application router deployed to Cloud Foundry, that is serving an index.html file, where we added some UI elements in the form of UI5 controls. The approuter is bound to an instance of the Authorization and Trust Management Service (xsuaa) in Cloud Foundry. This allows us to call the /user-api/currentUser endpoint and get information about the logged in user. All of this routing configuration is defined in the xs-app.json file:

{
    "welcomeFile": "index.html",
    "authenticationMethod": "route",
    "routes": [
      {
        "source": "^/user-api(.*)",
        "target": "$1",
        "service": "sap-approuter-userapi"
      },
      {
        "source": "^(.*)$",
        "target": "$1",
        "authenticationType": "xsuaa",
        "localDir": "./"
    }
    ]
  }

 

Adding a New Route

We will now extend this xs-app.json file with a new “^/backend(.*)” route. We want the approuter to forward all requests that hit that route to the destination that will be bound to the application. We therefore add a new route to the xs-app.json file as the second route in the array, before the “^(.*)$” route. The order of the routes does matter, because the approuter compares the requests and routes from top to bottom and will select the route that matches first.

{
   "welcomeFile": "index.html",
   "authenticationMethod": "route",
   "routes": [
      {
        "source": "^/user-api(.*)",
        "target": "$1",
        "service": "sap-approuter-userapi"
      },
      {
        "source": "^/backend(.*)",
        "target": "$1",
        "destination": "backendDestination",
        "authenticationType": "none"
      },
      {
        "source": "^(.*)$",
        "target": "$1",
        "authenticationType": "xsuaa",
        "localDir": "./"
      }
   ]
}

The regular expression “^/backend(.*)” will also match requests such as /backend/Products, so we will be able to request specific recourse from that API.

In case you are wondering why “target”: “$1” is included in all the routes: It means that the target of the request will be the first capture group that matches the regular expression of the route. If we send a request /backend/Products, the first capture group that matches the “^/backend(.*)” regular expression is /Products, leaving us with “target”: “/Products”, which will be attached to the destination. Essentially, it makes sure that the word “backend”, which is just a placeholder for us, will not actually be sent to the destination.

Creating the Destination

Next, we want to create an instance of the Destination Service in Cloud Foundry. We will use the Cloud Foundry CLI in this case, but you could achieve the same thing using SAP BTP Cockpit.

First, we create a configuration file that holds the information about the destination (more info in the official documentation). We create the following dest-config.json file:

{
    "init_data": {
        "instance": {
            "existing_destinations_policy": "update",
            "destinations": [
		{
                    "Name": "backendDestination",
                    "Authentication": "NoAuthentication",
                    "ProxyType": "Internet",
                    "Type": "HTTP",
                    "URL": "https://services.odata.org/V4/Northwind/Northwind.svc/"
              }
            ]
        }
    }
}

Notice how the name of the destination matches the name we specified for the route in the xs-app.json of the approuter – this is very important. For the URL we specified the popular Northwind OData service, but feel free to replace this with any other REST based API you want to consume (What is REST?).

We can now go ahead and create an instance of the Destination Service using this configuration. Make sure to be logged in to your Cloud Foundry environment. We execute the following command to create the instance by specifying a service, service plan, instance name and configuration file:

cf create-service destination lite backendDestination -c dest-config.json

 

Binding the Application to the Destination Instance

We want to extend our manifest.yaml file so that our application will be automatically bound to the backendDestination instance during deployment. We add the service instance to the services section of the file, so it looks like this:

---
applications:
- name: my-web-page
  buildpack: https://github.com/cloudfoundry/nodejs-buildpack
  random-route: true
  services:
  - my-xsuaa
  - backendDestination

We also added the “random-route: true” attribute to the configuration, which will make sure our application will have a unique URL. Someone else might have already deployed an application called “my-web-page” your region and we want to avoid conflicts.

 

Deploying and Testing Application

We can now go ahead and deploy the application using the cf push command, just like we did in the previous post. In the terminal output, we will get the URL (route) to our application:

When visiting the URL, we don’t see any visible changes compared to the previous post, which is fine, since we did not touch the UI at all. The new feature we implemented unveils itself if we attach our /backend route to the URL of our application:

The approuter forwards the /backend request to the Northwind OData service and sends the response back to our application. We can see the Northwind service, but looking at the URL it appears as if is “part of our application”. This is very important considering the CORS policy of modern browsers. Probably every web developer has encountered this problem at least once, and this is one way to solve it.

We can also try and request specific entities of the Northwind service such as /Products, which also works:

 

And that’s it. We can now use our approuter to consume data from an external API using the Destination Service in Cloud Foundry. We can simply call a relative URL (/backend/Products) from the application to get the data.

Check out the next blog post of this series.

Feel free to reach out in case you have any questions.

Stay tuned for more blog posts covering the basics of Cloud Foundry.

 


sap-tech-bytes

 

 

 

 

SAP Tech Bytes is an initiative to bring you bite-sized information on all manner of topics, in video and written format. Enjoy!

Assigned Tags

      6 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Piotr Tesny
      Piotr Tesny

      Hello Nicolai Geburek ,

      Thank you for this excellent and very comprehensive write-up.

      Could you please point me to documentation that explains the xs-app.json routes syntax in more detail?

      You explain nicely the logic behind the regular expression syntax of the source of a route and how the target value (to be appended to the destination's  URL) is derived from it to make the right business call.

      I know the destination definition syntax allows for injecting headers and/or url params to both the destination itself and along the business call URL.

      Is there a way to pass additional headers and/or params to a destination's definition from xs-app.json ?

      many thanks; Piotr

      Author's profile photo Nicolai Schoenteich
      Nicolai Schoenteich
      Blog Post Author

      Hi Piotr Tesny ,

      Thanks a lot.

      Sure, this is the link to the documentation:

      https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/c103fb414988447ead2023f768096dcc.html?locale=en-US

      I am not aware of any way to modify the headers via the xs-app.json configuration. For fancy things you can always extend the approuter though.

      BR, Nico

      Author's profile photo Piotr Tesny
      Piotr Tesny

      Thanks Nicolai Geburek

      Very useful links to official SAP documentation.

      A quick question: I have been reading the Launchpad service documentation Configure Application Routing (xs-app.json) and am curious how one can pass a user JWT token to a Launhpad's HTML5 application thus effectively bypassing the managed application router ?

      In other words would you know what should be the syntax of the xs-app.json of a static HTML5 target application so I can call the target from a non-BTP application passing the target a user JWT token thus effectively bypassing the manage app router mechanism ?

      I know how to do it if the target application is a nodejs/java/docker based micro-service that is protected by an OAuth token. But I am struggling to see how I can add a custom OAuth protection to an existing static HTML5 content via in xs-app.json ?

      Here goes the quote from the official SAP documentation.

      User Authentication Services

      User Account and Authentication (UAA)

      User authentication is performed by the User Account and Authentication (UAA) server. In the run-time environment (on-premise and in the Cloud Foundry environment), a service is created for the UAA configuration.

      • Only if there is no JSON Web Token (JWT) available, for example, if a user invokes the application from a Web browser, a calling component accesses a target service by means of the managed application router.

      • If a JWT is already available (for example, because the user has already been authenticated), or the calling component uses a JWT for its own OAuth client, the calling component calls the target service directly; it does not need to use the managed application router.
      Author's profile photo Nicolai Schoenteich
      Nicolai Schoenteich
      Blog Post Author

      Hi Piotr,

      Do I get this right? You want to call an HTML5 application that sits behind a managed approuter and is protected via OAuth, but you want to handle the authorization yourself by passing a valid JWT token and therefore not really interact with the approuter? This is possible and also required for example when testing OAuth protected endpoints via Postman. This is what the route for such an endpoint could look like (nothing special at all):

      {
        "source": "^(.*)$",
        "target": "$1",
        "service": "html5-apps-repo-rt",
        "authenticationType": "xsuaa"
      }

      I believe this is what you are looking for:

      https://blogs.sap.com/2020/03/02/using-postman-for-api-testing-with-xsuaa/

      BR, Nico

      Author's profile photo Piotr Tesny
      Piotr Tesny

      Hi Nicolai,

      Thanks. Indeed this is precisely what I want to achieve.

      But having

      "authenticationType": "xsuaa"

      causes a redirect to the BTP sub-account login page...

       

      Thus I want to use an OAuth2SAMLBearerAssertion destination to generate an access bearer token. In other words my xs-app.json would look like like this:

      {
        "welcomeFile": "/index.html",
        "authenticationMethod": "route",
        "routes": [
          {
            "source": "^(.*)",
            "target": "$1",
            "destination" : "Quovadis",
            "authenticationType" : "none",
            "service": "html5-apps-repo-rt"
          }
        ]
      }

      My non-BTP application would call the destination, retrieve its payload and pass the bearer access token in the Authorization header of the call to the html5 service repo....

      thx; Piotr

      Author's profile photo Piotr Tesny
      Piotr Tesny

      Hello Nicolai Geburek ,

      Maybe you can help with the following conundrum: how to append on the fly the .html extension on all child web pages (but the main index.html page) being served via approuter ? It is likely some regEx formula to be applied on source and target properties of a route in the xs-app.json file.

      FYI: I posted this question with ample explanations here

      kind regards;Piotr