Skip to Content
Technical Articles
Author's profile photo Carlos Roggan

SAP Cloud Platform Backend service: Tutorial [26]: App Router (3): route to Backend service

Quicklinks:
Summary
Reference
Sample project files

This is the next round of our Application Router learning.
In the first part: the basics
In the second part: add user login
In this part: use one login and reuse it to call a protected API

Everything is done without one single line of code

Goal

Create an Application Router configuration app which routes to an API in Backend service
The advantage:
When using App Router, the user doesn’t need to do manual complex token handling
Remember: When directly calling a Backend service API, the user needs to manually follow the OAuth flow requirements

Prerequisites

Coding skills: not required at all
App Router Tutorial part 1: sure, required
App Router Tutorial part 2: recommended
Backend service tutorials: obviously, one is required, all others are just good
Configuration in cloud: access role for API is required

We want to call an API in Backend service. (see here)
In my examples, I’m using a service called PRODUCTSERVICE
As such, when I say “Backend service API”, I mean an OData service like this:
https://backend-service-api.cfapps.eu10.hana.ondemand.com/odatav2/DEFAULT/PRODUCTSERVICE/Products
It was created with SAP Cloud Platform Backend service
If you’ve created an API, you must have configured your user roles as required (see here)

Preparation

You can reuse the existing app from previous tutorials, because we’re just adding another route (and destination)
Anyways, you can also create the same project structure again, then deploy with different name

XSUAA service instance
In previous learning 5, we’ve already created an instance of XSUAA, which was required to enable authentication/authorization
Now, for calling our API in Backend service, we need an XSUAA instance which contains the required scope.
Background: an API in Backend service is protected via OAuth and to call an API, the caller needs to have a certain role.
This required role is defined by Backend service. We need to add a reference to that role in our XSUAA instance
For more Background, see here – and for configuring the xsuaa instance, see here 

Summary:
Create an instance of XSUAA with name “XsuaaForAppRouterWithBs” and these parameters:

{
   "xsappname": "XsuaaForAppRouterWithBs",
   "tenant-mode" : "dedicated",
   "foreign-scope-references": [
     "$XSAPPNAME(application,4bf2d51c-1973-470e-a2bd-9053b761c69c,Backend-service).AllAccess"
   ]
}

Note:
tenant-mode dedicated is required, otherwise host pattern need to be specified

After creation, add the dependency to the manifest.yml file:

  . . .
  path: appfolder
  services:
  - XsuaaForAppRouterWithBs
  . . .

Note:
If you already have a dependency to XSUAA service, make sure to replace it

Note:
If you’ve already deployed this app, while following the tutorial, it is better to undeploy the app from Cloud Foundry, then redeploy from scratch
It doesn’t take more time
Delete either with delete command (cf d <myApp> -r -f) or pressing the “delete”-icon in the “Applications” overview in Cloud Cockpit

Learning 8: Route to Backend service API

Why another chapter? We’ve already defined lots of routes…
This title is a bit understatement
It should be: Route to an OAuth-protected API
If you’ve read the blog about calling a Backend service API from remote application, then…
No, I haven’t
You should
Only should? No must?
You should read it, it is a must-read
Ehmm
OK, the blog about calling a Backend service API from remote application shows that calling the Backend service API is not simple, because it is protected by OAuth 2.0
The security-mechanism of Backend service APIs require a JWT token. It has to be obtained from “Authorization Service”, etc
For explanation see here and here
Apart from marketing…
I know the question: what does it have to do with App Router?
Yes, that was the question
So how do the great AppRouter blogs relate to the fantastic OAuth blogs?
That was not really the question…
The answer:
In the wonderful previous tutorial, we’ve learned that we can use App Router for authentication and authorization. So, App Router can display user login screen
In such scenarios, App Router is bound to an instance of XSUAA
In such scenarios, the XSUAA is used as “Authorization Server” for App Router.
It issues a JWT token for access to App Router

The good question: Why not just reuse the JWT token for authentication to Backend service?
And, the good answer: It is possible
Another good question: how can the magic be done?
Another good answer: in the App Router config: in the configured destination
Short question: how?
Short answer: forwardAuthToken property

See the diagram for simplified flow:

Before my (always impatient) alter ego starts complaining, let’s do it:
It is short: just add a property to the destination

“forwardAuthToken”: true

manifest.yml

Add a destination pointing to your Backend service API with entity set
It contains the forwardAuthToken property:

  services:
  - XsuaaForAppRouterWithBs
  env:
    destinations: >
      [
          {
              "name": "env_destination_bs",
              "url": "https://backend-service-api.cfapps.eu10.hana.ondemand.com/odatav2/DEFAULT/PRODUCTSERVICE;v=1/Products/",
              "forwardAuthToken": true
          }

xs-app.json

Specify a new route:

    {
      "authenticationType": "xsuaa",
      "source": "^/backendservice/(.*)$",
      "target": "$1",
      "destination": "env_destination_bs"
    }

Deploy & Test

After deploy, open the App Router route.
For example:
https://myapprouterconfappbs.cfapps.eu10.hana.ondemand.com/backendservice/

In the login screen, enter the user credentials of a valid Cloud Platform user, e.g. your Trial Account user

Note:
The user must have the required role for accessing Backend service API (as mentioned above)

Afterwards, the expected payload of the OData service should be displayed.

That’s it

Cool, it was easy!
Yes, it was so easy: only by adding the little forward-property to the destination, we were able to overcome the OAuth protection of Backend service
“Overcome”??? Does that mean that we’ve hacked the API???
No, no, don’t panic
Everything is fine: there was the login-screen displayed by XSUAA server
So why do you say “overcome”?
Because for an end-user it is tedious to handle the OAuth flow from external application
Now by using App Router, it is much easier: end-user only needs to enter his credentials
But…. There’s a prerequisite
The correct App Router-configuration

Troubleshooting:

If you get an error message saying that you don’t have “sufficient authorization”, then there’s something wrong with the authorization
Obviously…
I see 2 possible error causes:
XSUAA:
Either the Backend-service-scope is missing, or the App Router is not bound to the correct XSUAA
User:
Check if the end-user has the Backend-service-role assigned
And one more possible error cause:
Check if the App Router configuration is correct

If you get an error message during deploy “No UAA …”:
Check if there’s anything wrong with the XSUAA instance
Check if  the binding of your app to the service instance has been done
If everything looks fine, you can probably solve the error by deleting the app and re-deploying

 

Learning 9: Calling Backend service via App Router from external REST client tool

We can still improve the learning of above chapter
Still not reached the end?
2 aspects to improve
– We cannot use REST client, because the App Router itself is OAuth protected
– the destination URL is hardcoded, pointing to one entity set

Route protection

The interesting point to learn here:
We can change the type of authentication for our route from “xsuaa” to “basic”
Like that, our entry to App Router is protected with “Basic authentication” instead of OAuth
Like that, we can easily use a REST client tool for executing e.g. POST requests

    {
      "authenticationType": "basic",
      "source": "^/srv/(.*)$",
      "target": "$1",
      "destination": "env_destination_odataservice"
    }

Route destination

Until now, we could call the App Router endpoint and we received the response of the OData entity set. But we weren’t able to perform e.g. a single READ (details of one entry)
See below for possible OData URLs
We want to use the App Router endpoint similar like the final OData service. As such, we need more flexibility

So now, we change the destination URL: it should point to the root URL of the OData service:

          {
              "name": "env_destination_odataservice",
              "url": "https://backend-service-api.cfapps.eu10.hana.ondemand.com/odatav2/DEFAULT/PRODUCTSERVICE;v=1/",
              "forwardAuthToken": true
          }

 

Deploy

As usual, after modifying the configuration, delete the deployed app in Cloud Foundry and redeploy the application

Test in browser

After deployment, open a browser and enter the app URL with appended route (/srv/):

https://myapprouterconfappbs.cfapps.eu10.hana.ondemand.com/srv/

Result:
This time we don’t get the user login page. This time we get a user credentials popup
What’s the difference?
This time no redirect to XSUAA (XSUAA acting as OAuth “Authorization Server”)
Just “basic authentication” is required
If we check the URL of the browser, we can see that the URL hasn’t changed
If we check the dev tools of the browser, we can see that no redirect has happened

The good thing:
With our App Router configuration, we can call the OData URIs as we’re used to
Examples:

https://myapprouterconfappbs.cfapps.eu10.hana.ondemand.com/srv/$metadata
https://myapprouterconfappbs.cfapps.eu10.hana.ondemand.com/srv/Products
https://myapprouterconfappbs.cfapps.eu10.hana.ondemand.com/srv/Products(‘1’)
https://myapprouterconfappbs.cfapps.eu10.hana.ondemand.com/srv/Products(‘1’)/categoryLink

Note:
They look like OData service URLs, but in fact they are APP Router URLs

Test in REST client

To fire requests against the App Router app, just proceed as for normal OData service

Enter the URL .e.g https://myapprouterconfappbs.cfapps.eu10.hana.ondemand.com/srv/Products
Configure the Authorization type as “Basic” and enter your (Trial) user and password
Then just hit send
Cool.
No tedious token-fetching anymore

Now try with POST/PUT/DELETE
You’ll see that everything just works like you’re used to

Stop! Help!
What’s the problem?
I’m getting “Forbidden”
What have you done?
Nothing
Which steps were required for nothing?
Just POST request – like I’m used to
Aha, and the x-csrf-token?
Aha, the x-what-token?
I see.
I don’t see
Listen:
As you know, for security reasons, modifying requests are secured by x-csrf-token
No
No what?
I didn’t know
OK, you need to send that token whenever you fire a POST/PUT/PATCH/DELETE request
How does it look like?
e.g.: x-csrf-token: 13f7f69080d0f6fd-MGG3fnoL21k5cse1hQ86wA0R4s=
How do I get it?
GET it with x-csrf-token: fetch (See appendix)
Which URL?
Can be any valid URL
E.g. www.google.com?
No, a URL of your service, e.g. the $metadata document
https://myapprouterconfappbs.cfapps.eu10.hana.ondemand.com/srv/$metadata

Disable the CSRF protection

The very last section.
Finally
If circumstances permit, we can disable the csrf protection (e.g. for testing phase)
This is an App Router setting, defined on route level
As per default, the csrf protection is enabled
We can disable it by adding the following property to our route:

“csrfProtection”: false

For trying it, we can define an additional route, e.g.

    {
      "authenticationType": "basic",
      "csrfProtection": false,
      "source": "^/srvnotoken/(.*)$",
      "target": "$1",
      "destination": "env_destination_bs_api"
    }

This has been a nice learning:
No access token
No x-csrf-token
Wonderful….

Troubleshooting

If your request in REST client doesn’t work, it returns status 200 but the response body contains the javascript for displaying a login form, although you’ve entered the correct credentials
-> then the reason might not be wrong credentials, it might be that you’ve entered the wrong route in the URL of your request

Summary

We want to call an API defined in SAP Cloud Platform Backend service
That API is protected with OAuth 2.0
This means, that for calling it, an access token must be sent
Instead of fetching the token ourselves, we can use App Router
At beginning, the App Router displays user login screen to get access token
The App Router supports forwarding that access token to target service
We can define a route to Backend service API, incl forwarded token

A nice benefit:
It is possible to configure the route with authentication type as “basic”, but still forwardAuthToken
Basic authentication is very useful when using a REST client tool

Even the csrf protection can be disabled, to make life even easier

Links

SAP Help Portal: documentation to configure csrf protection in app router: here

Interesting readings:

App Router tutorial 1
App Router tutorial 2
OAuth explanation 1
OAuth explanation 2
Manually calling an API usind REST client

All the other blogs about SAP Cloud Platform Backend service

 

Appendix: Reference

The App Router configuration:

xs-app.json:
Enable authentication on top-level:
“authenticationMethod”: “route”,
and specify on route-level
“authenticationType”: “xsuaa”,
or
“authenticationType”: “basic”,

manifest.yml:
enable forwarding
“forwardAuthToken”: true

Bind app to XSUAA instance
XSUAA instance must contain the scope which is required by Backend service API

To disable the csrf protection: specify on route-level
“csrfProtection”: false

Appendix: x-csrf-Token

If you fire requests which modify the data of a service, you have to be aware that it might require an x-csrf-token header.
As such, if your POST, PATCH, PUT, DELETE request fails, you can check the response if there’s a header:
x-csrf-token : required

If yes, proceed as follows:
Execute a valid GET request and add a header
x-csrf-token : fetch

The response contains the valid token in a header
x-csrf-token : 123412324134

Copy the value and send your intended request (e.g. POST) with header:
x-csrf-token : 123412324134

Appendix: sample project files (Learning 9)

xsuaa-params

{
   "xsappname": "XsuaaForAppRouterWithBs",
   "tenant-mode" : "dedicated",
   "foreign-scope-references": [
     "$XSAPPNAME(application,4bf2d51c-1973-470e-a2bd-9053b761c69c,Backend-service).AllAccess"
   ]
}

The full command:
(adapt myInstanceName and myXsappname)

cf create-service xsuaa application myInstanceName -c “{\”xsappname\”:\”myXsappname\”,\”tenant-mode\”:\”dedicated\”,\”foreign-scope-references\”:[\”$XSAPPNAME(application,4bf2d51c-1973-470e-a2bd-9053b761c69c,Backend-service).AllAccess\”]}”

Note:
The id in foreign scope is valid for Trial account

manifest.yml

---
applications:
- name: ApprouterToBackendservice
  host: tobs
  path: appfolder
  memory: 128M
  services:
  - XsuaaForAppRouterWithBs
  env:
    destinations: >
      [
          {
              "name": "env_destination_bs_api",
              "url": "https://backend-service-api.cfapps.eu10.hana.ondemand.com/odatav2/DEFAULT/PRODUCTSERVICE;v=1/",
              "forwardAuthToken": true
          }
      ]

xs-app.json

{
  "authenticationMethod": "route",
  "routes": [
    {
      "authenticationType": "basic",
      "source": "^/srv/(.*)$",
      "target": "$1",
      "destination": "env_destination_bs_api"
    }
  ]
}

package.json

{
  "name": "approuterconfigapp",
  "scripts": {
    "start": "node node_modules/@sap/approuter/approuter.js"
  },
  "dependencies": {
    "@sap/approuter": "^6.0.1"
  }
}

 

Assigned Tags

      16 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Minh Tri Le
      Minh Tri Le

      Hi Carlos Roggan ,

      It's very helpful.

      One more question is, for example, I have a UI5 application and need to query data from the Backend service API. How can I integrate UI5 application with the app router?

      Regards,

      Tri

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi Minh Tri Le ,
      Sorry for the late reply, and thanks for your question.
      Now I have tried it and please refer to below blogs for my findings:

      https://blogs.sap.com/2019/07/15/sap-cloud-platform-backend-service-tutorial-30-api-called-from-html/

      https://blogs.sap.com/2019/07/16/sap-cloud-platform-backend-service-tutorial-31-api-called-from-ui5/

      And thanks for your feedback !!
      Kind Regards,

      Carlos

      Author's profile photo Minh Tri Le
      Minh Tri Le

      They're very helpful.

      Thanks a lot Carlos Roggan .

      Author's profile photo Mathias Maerker
      Mathias Maerker

      In case anybody encounters the 403 while working wit SuccessFactors OAuthSaml2BearerAssertions disblae csrf in your route, approuter is handling that internaly, everything else does not work

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      super, thanks for the contribution !!

      Author's profile photo Anand Kolte
      Anand Kolte

      Hello,

      In case if the service has to be accessible from ui5 and as an API both, how do we set the authenticationType in xs-app.json?

      Will it accept both "xsuaa" and "basic"?

       

      Thanks,

      Anand

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      I don't think that's supported, but what you can do would be to define 2 routes with different auth and pointing to the same destination.
      KInd Regards,
      Carlos

      Author's profile photo Anand Kolte
      Anand Kolte

      Thanks Carlos for your reply!

      I tried with another route in xs-app.json going to same destination. But it gives me an error -

      "Cannot GET myBackendServ/$metadata"
      My service is exposing projections on entities that generate Hana cloud tables.
      My xs-app.json looks like below.
      {
      "authenticationMethod": "route",
      "routes": [
      {
      "source": "/myServ/(.*)",
      "destination": "srv-api",
      "csrfProtection": true,
      "authenticationType": "xsuaa"
      },
      {
      "source": "/myBackendServ/(.*)",
      "destination": "srv-api",
      "csrfProtection": true,
      "authenticationType": "none"
      },
      {
      "source": "/(.*)",
      "localDir": ".",
      "authenticationType": "xsuaa"
      }
      ]
      }
      
      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hello Anand Kolte ,

      I think the $1 and the target property are missing

      Author's profile photo Anand Kolte
      Anand Kolte

      Thanks Carlos Roggan , that worked!

      Author's profile photo Pradeep T N
      Pradeep T N

      Hello Carlos Roggan

      Thanks for the blog post.

      I am facing an issue in our application. when the browser language is set to 'Japanese', the application shows blank page. On investigating it is found that sap-ui-core.js library is not loading properly. And after checking with SAP UI5 teams, they observed that it is because of the content type header value modified to "text/html;charset=Shift_JIS", which is causing the issue.

      It should have been "text/html;charset=utf-8".

      Is there any way I can add content type in the approuter while returning the html file ?

      Thanks
      Pradeep

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hello Pradeep T N ,

      sorry for the late reply - I was on vacation.
      I remember from the approuter docu the support for custom headers - that could be used to serve your needs, maybe?
      See here:
      https://www.npmjs.com/package/@sap/approuter

      Kind Regards,
      Carlos

      Author's profile photo Devin Bui
      Devin Bui

      Hello Carlos Roggan,

      Thanks for the blog post!

      I have a scenario that I am holding a JWT (issued by SAP) and I can use Postman to call directly to my backend app as expected (without using the app router).

      Now, a new requirement coming, I need to use the app router to forward the JWT to the backend app.

      Here is my configuration for the app router

      package.json 

      {
      	"name": "myapprouter",
      	"scripts": {
      		"start": "node node_modules/@sap/approuter/approuter.js"
      	},
      	"dependencies": {
      		"@sap/approuter": "^6.0.1"
      	}
      }

      Create xs-app.js and use "authenticationMethod": "none" 

      {
      	"authenticationMethod": "none",
      	"routes": [
      		{
      			"source": "^/(.*)$",
      			"destination": "env_destination_mybackendapp"
      		}
      	]
      }

      manifest.yml

      In this step I use "forwardAuthToken": true (I am expecting the app router can forward the JWT to the backend API app)

      ---
      applications:
      - name: myapprouter
        host: myhostapprouter
        path: approuter
        memory: 128M
        env:
          destinations: >
            [
                {
                    "name": "env_destination_mybackendapp",
                    "url": "https://mybackendapp.cfapps.us10.hana.ondemand.com/hello/",
                    "forwardAuthToken": true
                }
            ]

       

      Using Postman and calling directly to my backend app with the Bearer JWT (issued by SAP), I get the response as expected.

       

      Then, call the app router and pass the same Bearer JWT in the request header, but it returns a Not Found error.

      I am looking for a way to configure the app router to forward the Bearer JWT to my backend app. But, I think my configuration is incorrect, or the app router cannot work in this way.

      Why do I have this scenario?

      In my system, there are some non-GUI apps (i.e., web services, OS services,...) and they cannot display the login form to enter credentials. So, I have to use JWT issued by SAP mentioned above to call my backend app hosted in SAP BTP. And it works without using the app router.

      Why do I need to use the app router in this scenario?

      There is some business logic I need to put into the app router (i.e., license validation,...). So, the app router is added in this scenario.

       

      I am expecting the app router will only forward JWT to my backend app.

      Does the app router support this scenario? Or, is there an alternative way to solve this challenge?

       

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hello Devin Bui

      as far as I understand, your scenario is similar to what I had investigated some time ago:

      https://blogs.sap.com/2020/02/27/using-job-scheduler-in-sap-cloud-platform-4-using-app-router/

      I assume, you'll find the solution there.
      As far as I remember, I did few simple config in approuter, such that it fust forwards the token.
      I really hope this helps you.

      You've very well described your scenario, top !

      Kind Regards,
      Carlos

      Author's profile photo Devin Bui
      Devin Bui

      Thank you for your help, Carlos Roggan!

      After updating xs-app.js as below, it works.

      {
      	"authenticationMethod": "none",
      	"routes": [
      		{
      			"source": "^/vndevapprouter/(.*)$",
      			"target": "$1",
      			"destination": "env_destination_saphome",
      			"scope": "$XSAPPNAME.Display",
      			"authenticationType": "none"
      		}
      	]
      }
      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Thanks for the update Devin Bui !
      Good to know that your scenario is working and also thanks for the description, good to know about this use case.

      Kind Regards,

      Carlos