Skip to Content
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"          
        }
      }
    ]
  }
}

 

2 Comments
You must be Logged on to comment or reply to a post.
  • 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?