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: 
CarlosRoggan
Product and Topic Expert
Product and Topic Expert

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"
}
}

 
16 Comments