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