Technical Articles
SAP Cloud Platform Backend service: Tutorial [33]: App Router (4): Run locally
Quicklinks:
App Router Chapters
Quick Ref
Sample project files
One last round of our Application Router learning.
After we’ve learned several basic configuration options, we’ve been ready to use the Application Router.
Today, we’re dedicating a blog for enhancing development experience:
How to use app router in local development ?
As always: you can skip this blog if you’re exigent and prefer sophisticated blogs – or if you never work locally
Your choice…
Prerequisite
You need Node.js on your local machine
You should have gone through Part 1
Preparation
You can go through Part 1 if not already done
Learning 10: How to run app router locally
As usual, we start with a simple learning:
Install app router locally and define a simple route
It is similar like learning 1
10.1. Install app router locally
To be honest, there’s nothing special about local app router installation:
Same as before, we define a dependency to the app router module in package.json file
OK, so we create a folder, e.g. C:\tmp_approuter_loc
No subfolder, since we’re not using a manifest
In folder C:\tmp_approuter_loc create a file with name package.json
In folder C:\tmp_approuter_loc create a file with name xs-app.json
Paste the following content into the package.json file:
{
"dependencies": {
"@sap/approuter": "^6.8.2"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
}
}
You can see: it is the same like before.
The only difference to cloud-scenario: today we really need to install the libraries
(When deploying to the cloud, the libraries are downloaded there, if not present
Open command prompt, navigate to folder C:\tmp_approuter_loc
and run npm install
That’s it: we’ve installed app router locally
Have you noticed that it is all the same like in previous tutorials?
However, now we need to pay attention to the configuration
10.2. Configure app router for local usage
In previous tutorials, we’ve learned that configuration is done in 2 files:
xs-app.json
No special configuration required here for local app router
{
"authenticationMethod": "none",
"routes": [
{
"source": "^/saproute/(.*)$",
"target": "$1",
"destination": "env_destination_saphome"
}
]
}
manifest.yml
Here we define the destinations which are used in xs-app.json
So NOW we have a problem: manifest.yml is used only in cloud
During deployment, the destinations become part of the environment of our deployed app (in this case it is the app which contains the app router)
When using app router locally, we have no deployment, manifest.yml is useless.
But nevertheless, we need the destinations as environment variable in the node process which runs the app router
Fortunately…
this is not a big problem:
app router has built-in support for local mode:
it can load required environment variables from a file with name default-env.json
As such,
We create a file with name default-env.json
in the root folder, next to package.json (this is from where we start the app router)
The content of this file is a JSON object with the same structure like the environment variables in SAP Cloud Platform
For our simple example, we copy the following content into the default-env.json file
{
"destinations" : [
{
"name": "env_destination_saphome",
"url": "http://sap.com/"
}
]
}
10.3. Start app router locally
Now we can start the app router.
How to start it locally?
This step is simple as well:
App router is a server which can be started using the command which we defined as “start script” in the package.json
Remember?
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
To start app router on our local machine, we open a command prompt, step into C:\tmp_approuter_loc
and run the following command: npm start
This command will start the app router and in the console we see some output like the following:
During startup, app router writes some interesting info about its settings to the console
The log output which is interesting for us: the last line. We can see that the app router – server is running on port 5000
10.4. Using local app router
Now we can open a browser window and invoke the route which we defined above.
We know that the URL has to start with localhost:5000
And we know the route we defined: saproute
As such, we can invoke the following URL with the Browser:
http://localhost:5000/saproute/
As a result, the sap homepage is displayed
Learning 11: How to use xsuaa with local App Router
Our first very simple example has already shown the mechanism how cloud-specific settings are passed to app router in local scenario:
using a special file
Now the second challenge: security
We know that the most important feature of app router is to do the OAuth flow for us.
I mean, app router can connect to the Authorization Server and handle the authentication, acquire a JWT token and forward it to the configured endpoint
To achieve that, the approuter-app is bound to an instance of XSUAA – service
As such, after deployment, app router can connect to that xsuaa
Under the hood:
After deployment, the app which is bound to xsuaa receives the needed info as environment variable
The environment variable VCAP_SERVICES contains the address and credentials required to connect to the XSUAA (Authorization Server) to handle the OAuth flow
App router can read the content of the environment variable doing something like process.env
(we know, app router is implemented in Node.js)
We see: we can make use of the same mechanism like we did for the destinations
We can copy the value of VCAP_SERVICES to our default-env.json file
Then the app router can handle OAuth flow even when running locally
Let’s try it
11.1. OAuth protected target service
To try our scenario, we need an OAuth protected service which we want to call with the help of app router
Such target service can be an OData service created with “SAP Cloud Platform Backend Service”
Or it can be any other service running on SAP Cloud Platform
Or it can be any other service running locally (probably using the same mechanism, to connect to xsuaa from local server)
What do you prefer?
Your choice.
What I’m doing:
In this blog, I’d like to avoid repeating the Backend-Service-stuff, so I’m using a service running in the cloud.
My example service URL:
https://myapp.cfapps.eu10.hana.ondemand.com/mysrv/Products
I try to invoke the URL: no chance, it will respond with “Unauthorized”
Yes, it does so.
Even for me
And that’s good.
Because we need it for the show
11.2. Configure local app router with XSUAA
Now what we want:
Use app router in the middle between us and the protected service
What we would do in “normal” mode:
Deploy app router to the cloud and bind it to the same instance of xsuaa
What we do in “local” mode:
We need the credentials of that “same xsuaa” on our local machine
We have to view and copy the environment variable for xsuaa
To achieve that, we have 2 options:
1. Check the environment variables of that protected service (called myapp in my example)
2. Create Service Key for the same xsuaa instance used by the protected service
Description
11.2.1. View environment variable
On command line: cf env myapp
Using Cloud cockpit:
Navigate to the details screen of the deployed app
In the left menu pane click on ‘’Environment Variables”
OK, now that everybody has seen the environment variables, we can copy the JSON
Note:
We need a note about copy&pasting
If we copy&paste the whole Environment Variables section, then we get an error
Reason: the Environment Variables section is not one valid JSON object.
It is two valid JSON objects
And they’re following each other
See the coarse structure:
{
"VCAP_SERVICES": {
. . .
}
}
{
"VCAP_APPLICATION": {
. . .
}
}
As such, when copying, make sure to copy only this object:
{
"VCAP_SERVICES": {
. . .
}
}
Note:
This is a sub-note of the copy&paste-note:
If the app is bound to multiple services, then the VCAP_SERVICES object can get very big.
However, for our app router scenario we need only the xsuaa section
You can reduce the JSON,
or copy&paste all
your choice
11.2.2. Create Service Key
Alternatively, you can get the required credentials via service key
I don’t know why you should follow this description, the previous alternative is faster,
but:
your choice
On command line:
To create service key: cf csk xsuaaName myServiceKey
Then to view its content: cf service-key xsuaaName myServiceKey
Result: a JSON object which we can copy&paste
Note:
See below note about copy&pasting service key-json
In cockpit:
Go to your space -> Services -> Service Instances
Click on the instance of xsuaa service -> Service Keys
Create a Service Key with any name: your choice
Click into the section with created service key -> Ctrl+A -> Ctrl+C
Note:
Again, we need a note about copy&pasting:
A service key is only a key, not the environment variable which we need
As such, those of you who had chosen to use the create-service-key-way, have to manually create the required structure:
{
"VCAP_SERVICES": {
"xsuaa": [
{
"tags": [
"xsuaa"
],
"credentials": {
"clientid": "sb-thexsappname!t12345",
. . .
You can see:
The content of the “credentials”-node is the service-key itself
Note:
The “tags” property is required because this is how app router finds the proper node, while parsing the JSON
How to copy&paste:
We have already a default-env.json file which already contains a valid JSON object.
We have to make sure to paste the copied VCAP_SERVICES correctly, otherwise the JSON parser will fail
The current structure:
{
"destinations" : [
{
"name": "env_destination_saphome" . . . .
}
]
}
We have to add a comma, then paste the new object.
Note:
The new object doesn’t have surrounding brackets:
{
"destinations" : [
{
"name": "env_destination_saphome" . . . .
}
],
"THE_NEW_OBJECT_GOES_HERE" : . . . . .
}
Finally after paste:
{
"destinations" : [
{
"name": "env_destination_saphome",
. . .
}
],
"VCAP_SERVICES": {
"xsuaa": [
{
"credentials": {
"clientid": "sb-thexsappname!t12345"
. . .
}
}
]
}
}
Very finally:
This is a sub-finally of the default-env-finally above:
To summarize:
what we’ve done is to copy a JSON object into a local file
Very note:
I don’t know if this is a sub-note, I’m confused…
Anyways, in the appendix, you can find all sample files
11.2.3. destination
Now that we’re prepared for local app router with xsuaa, we can do the usual route configuration
First we add a new destination to the manif……..Sorry, obviously we’re not using manifest
So we add a new destination to the destinations-node of our default-env.json file
The destination points to our protected service which we’ve chosen in 11.1.
In my example:
{
"name": "env_destination_mysrv",
"url": "https://myapp.cfapps.eu10.hana.ondemand.com",
"forwardAuthToken": true
}
Note:
Don’t forget to add a comma after the first destination, otherwise the JSON would be invalid
Note:
Don’t forget to add the property forwardAuthToken, because this destination is oauth-protected
Note:
Don’t forget to remember the name of the destination, we’ll need it in the route definition
Note:
Please refer to appendix section for full sample code
11.2.4. route
Now we can add a new route to our xs-app.json file
{
"source": "^/protectedroute/(.*)$",
"target": "$1",
"destination": "env_destination_mysrv",
"authenticationType": "xsuaa"
}
And important:
We have to change the top-level property authenticationMethod
from
"authenticationMethod": "none",
to
"authenticationMethod": "route",
See appendix…
11.3 Start local app router
If your app router is still running, you need to stop it
Otherwise it wouldn’t read the new configuration
As usual, press Ctrl + C, then Y
Then re-start it like described above, no special param is required
Make sure to watch the log output, because the startup would fail if the default-env.json file would have invalid JSON
11.4. Usage
Remember:
the forbidden URL which we wanted to call was (in my example)
https://myapp.cfapps.eu10.hana.ondemand.com/mysrv/Products
Now we can enter the following URL in a browser window
http://localhost:5000/protectedroute/mysrv/Products
As a result, a login screen is displayed
After entering our cloud-user credentials, we can see the desired result
Summary
For me, local app router was a huge benefit for local app development
It helps when defining routes, so we don’t need to deploy app router each time, when we fix anything in the configuration
If we’re running or developping an OAuth-protected service locally (e.g. with CAP) then we urgently need a local app router to avoid that we have to do the OAuth flow with REST client each time we want to try the app
It helps tremendously when working locally on an app which requires app router, e.g. ui5
Note about local development
You’ve noticed:
We have to distinguish between several levels of “local”
The app router is running locally
The XSUAA Authorization Server is running in the cloud
The target endpoint can be local server or remote server
So the described example was a mix of local development and remote Authorization Server
Quick Reference
For local app router we need:
– Install and configure normally via package.json and xs-app.json
– Add default.env.json containing destinations
– Start with npm start and access at localhost:5000
To use xsuaa:
– Get xsuaa credentials via app env or service key
– copy VCAP_SERVICES to default-env.json file
Links
SAP Help Portal: Application Router documentation entry page
Overview of tutorial series
Appendix 1: Background info about local configuration
If you’re interested to check how app router supports local scenario:
Go to folder
C:\tmp_approuter_loc\node_modules\@sap\approuter\lib\configuration
and open the file env-config.js
Here you can see that the library @sap/xsenv is used to load a file with hardcoded name default-env.json into the environment
The convenience method xsenv.loadEnv()just parses the json and sets all found properties as variables into process.env
Like that they can be accessed by app router
Appendix 2: Sample project files
See here exemplary files for the scenarios described above
package.json
{
"dependencies": {
"@sap/approuter": "^6.8.2"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
}
}
xs-app.json
{
"authenticationMethod": "route",
"routes": [
{
"source": "^/saproute/(.*)$",
"target": "$1",
"destination": "env_destination_saphome"
},
{
"source": "^/protectedroute/(.*)$",
"target": "$1",
"destination": "env_destination_mysrv",
"authenticationType": "xsuaa"
}
]
}
default-env.json
{
"destinations" : [
{
"name": "env_destination_saphome",
"url": "http://sap.com/"
},
{
"name": "env_destination_mysrv",
"url": "https://msgmock.cfapps.eu10.hana.ondemand.com",
"forwardAuthToken": true
}
],
"VCAP_SERVICES": {
"xsuaa": [
{
"tags": [
"xsuaa"
],
"credentials": {
"apiurl": "https://api.authentication.eu10.hana.ondemand.com",
"clientid": "sb-xsappname!t12345",
"clientsecret": "V7Qfqe4YxjsUEMwn-v/iGxxx=",
"identityzone": "abcdef123trial",
"identityzoneid": "b2d58db5-123-aaa-bbb123-8b23be70fd5c",
"sburl": "https://internal-xsuaa.authentication.eu10.hana.ondemand.com",
"tenantid": "b2d58db5-635f-44a2-aaa-bbb-ccce70fd5c",
"tenantmode": "dedicated",
"uaadomain": "authentication.eu10.hana.ondemand.com",
"url": "https://abcdef123trial.authentication.eu10.hana.ondemand.com",
"xsappname": "xsappname!12345"
}
}
]
}
}
Hey Carlos Roggan ,
thx for this blog post and the clarrification of the destinations array on the default-env.json.
That got me thinking. We do have a lot of destinations and the most of them are protected by an oAuth endpoint (e.g. running / hosted on the APIM).
So as the endpoints are not protected by xsuaa, instead the endpoints are protected by (own) policies (basic-auth, oAuth,..) this is missing in your easy example and I'm wondering, if we can extend the approuter for such (local) usecases!?
In node apps and even in FaaS, I'm calling the '/destination-configuration/v1/destinations/' service with my destination name and receive the authTokens within the URL of the destination configured in the destination-service on the space / subaccount.
Is there an example how to enhance the approuter for such things? Did you do something similiar already ?
Thx for your thoughts and feedback 🙂
BR,
Cedric
I was able to run approuter locally , connecting to remote cloud foundry backend service. I want to connect local approuter to backend springboot service running locally. Wondering how can springboot validate JWT token from approuter if it is running in local? can backend service connect to xsuua service to validate jwt tokens?
Hi Sam,
Were you able to make springboot application to validate JWT token from approuter?
Hi Carlos Roggan ,
Thanks for the great article. I tried the step one of approuter and it started in port 5000. But when I hit http://localhost:5000 it says ERR_CONNECTION_REFUSED.
Could you please help me? also is there any git reference link or sample I can refer to
Thanks
Raman
Hey Carlos Roggan ,
Your blog is really help and I can run the app router embeded frontend application succesfully now.
For the envioroment variable for xsuaa, I use cf env myapp and copy the vacap_services to the file default-env.json
The details is below:
But once I set the "forwardAuthToken": false as true, then the destination will be get as an 401, unauthorization, It can only be access when it is set as false.
How the forwardAuthToken works?
Is there is any error in my VCAP_SERVICES?
I have this confused.
Hi Bella Fang,
Were you able to resolve the 401 unauthorization error?
I am also stuck with the issue.
Let me know if you were able to resolve this.
Regards,
Medha
Hi Bella Fang,
were you able to resolve the 401 error? I am also stuck with this issue.
Regards,
Medha