Technical Articles
SAP Application Router
In this blog post I will try to help you to understand what is AppRouter, how to easily configure and consume it. I will also provide answers for frequently asked questions and solutions for possible issues.
What is Approuter:
AppRouter – is Node JS library that is available in Public NPM. It represents a single entry point to your application.
Which environments Approuter can be used in:
- SAP CF – Cloud Foundry
- SAP XSA – XS Advanced (On Premise)
- Local environment
Main capabilities and features:
- Dispatching of requests to other microservices
- Authentication
- Authorization check
- Complete integration with Destination service
- Complete integration with HTML5 Application repository
- Complete integration with Business Services
How to start:
- Inside you application’s package.json add the dependency on Approuter and configure its start point:
{
"dependencies": {
"@sap/approuter": "<APPROUTER_VERSION>"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
}
}
- Run npm install
- Now you have approuter code inside your node_modules, you can start work
How to Configure Approuter:
The main configuration for Approuter is done by file xs-app.json that needs to be created in the level of your AppRouter application. Next sections in this blog will help you to configure your Approuter correctly with various modeling options in your xs-app.json file.
Main properties on root level:
authenticationMethod
This property indicates which authentication will be applied for this xs-app.json. Can be none (means that all routes are not protected) and route (authentication type will be chosen according to definition in a particular route). The default value is route .
logout
By using this property, you can define two important thing about your business application central logout handling.
logoutEndpoint – contains some internal path. When accessing this path your application will trigger central logout procedure. Triggering central logout will destroy user session data in Approuter, call XSUAA in order to remove user session on their side and also will call logout paths of destinations that are defined for this specific application (please refer to property destinations).
logoutPage – can be internal path or absolute external URL. Value of this field describes so called “landing page” page address, that user will be redirected in the browser after central logout.
"logout": {
"logoutEndpoint": "/my/logout",
"logoutPage": "/logout-page.html"
}
Tip: In order to change browser location and trigger central logout from client-side Javascript code you can run:
window.location.replace('/my/logout');
For more information and tips please refer to section Logout flow .
destinations
This property indicates destinations endpoints that need to be called during session/central logout in order to destroy sessions on their side.
services
The same as destinations. Services can implement their specific logout logic and approuter will trigger these endpoints during central logout / session timeout scenario.
Routing:
One of important capabilities of AppRouter is to be a reverse proxy for your application. In order to achieve that you need to model correctly property routes . That property is an array of Objects. Each object represents one particular route.
Let’s see how your routes should be modelled.
source
First main property inside the route is “source”. The value can be string or regular expression that should match the incoming request URL. Example:
routes": [
{
"source": "/rest/.*"
This definition means that if the Approuter is called with request https:<approuter_host>/rest/… , this particular route configuration will be applied.
Tip: A request matches a particular route if its path contains the given pattern. To ensure the regular expression matches the complete path, use the following form: ^$`. Example:
"source": "^/rest/addressbook/testdataDestructor$"
Here we expect to have complete URL matching.
Tip: Be aware that the regular expression is applied to on the full URL including query parameters.
target
Second important property is “target” – it is an optional property that defines how the incoming request path will be rewritten for the corresponding destination. You can use capture groups inside regular expressions that are defined in “source” . Example:
"routes": [
{
"source": "/sap/com/(.*)",
"target": "$1"
This definition means that if the Approuter is called with request https:<approuter_host>/sap/com/server.js, it will be overwritten by AppRouter to https://<destination_url>/server.js.
destination
The name of destination into which approuter should redirect. Destination can be modeled in 3 ways:
- As an Environment variable in AppRouter
- As a Destination in your sub account in Cockpit
- In Service Instance of Destination Service
Destination modeling contains not only url but also can have timeout, proxy data and others. Important property here is “forwardAuthToken” . If you specify true, JWT / OAuth token will be forwarded to this particular destination. More details about JWT and OAuth tokens can be found in “Login flow” and “Integration with Destination Service” sections.
service
The name of bound business service approuter should call. It supports both grant_type “user token” and “client credentials” . In case business service requires authentication with grant_type “user token” – approuter will also exchange the user JWT before redirecting to the service. Approuter gets the information about business service by looking on this particular service binding information.
More details can be found in section “Integration with Business Services”.
endpoint
This property is relevant only for route with “service” property. Sometimes business service exposed several endpoints (urls) in its binding, for example for different usages. In such case you can specify a specific service endpoint name for the current route. More details can be found in section “Integration with Business Services”.
localDir
Only used in old classic Approuter mode. That means that static resources are part of Approuter project and are located inside one of your application folders (working directory). Later on after pushing these resources are placed in File System of your application container.
Recent approuter versions support full integration with CF HTML5 App service, which stores the resources in one central place in Object Store and has caching possibility due to integration with NGINX. This service exists in CF but not in XSA.
More details can be found in section “HTML5 App Repo service”.
Tip: make sure that requests to such routes are only of GET and HEAD types.
authenticationType
This String property is used for indicating which authentication should be applied for this specific route. The default value is “xsuaa” – that means that user will have OAuth2 login form authentication using redirect to UAA server. Other types are “basic” – for basic authentication (useful for SAP HANA users in XSA scenario) and “none” – that means that this route is not protected.
Tip: These configurations are relevant only if authentication is enabled on root level (“authenticationMethod” in root level of xs-app.json). More details can be found in “authenticationMethod” section.
csrfProtection
This boolean property is used for enablement or disablement of CSRF protection. Default value is “true”. Approuter enables such protection only for not public routes (routes that require authentication) and HTTP methods that are not GET and HEAD .
More details can be found in section “CSRF Protection”.
Tip: If you have route that targets to some backend that implements CSRF by itself, be sure to switch off explicitly csrf on this route in Approuter. In such case csrf token will pass directly between the browser and the backend.
scope
Approuter also executes basic authorization by comparing user’s scopes against scope that is defined for a particular route. This property can be String that represents a single scope or array of scopes names. This property holds the required scopes to access the target path.
{
"source": "^/web-pages/(.*)$",
"target": "$1",
"scope": ["$XSAPPNAME.viewer", "$XSAPPNAME.reader", "$XSAPPNAME.writer"]
}
cacheControl
This String property represents the content of “Cache-Control” header in Approuter response and is relevant only for static resources Approuter will serve that are stored in your project. By default, this header is not set.
Tip: Model this property for your main route in order to prevent caching it by browser. Otherwise, after logout your page will be retrieved from the Browser Cache without reaching Approuter and login flow will not take place.
{
"routes": [
{
"source": "^/ui/index.html",
"target": "index.html",
"localDir": "web",
"cacheControl": "no-cache, no-store, must-revalidate"
}
]
}
Important note: The order of routes is very important – AppRouter tries to find matching route one-by-one until it finds one appropriate.
How to start running Approuter:
You can simply run Approuter configured by you by pushing your application to CF/XSA
or by running npm start in your local environment.
Login flow:
One of main capabilities of AppRouter is authentication with SAP XSUAA. The most common authentication type is OAuth2 authentication. Whenever users types URL of his application that uses AppRouter, login flow is taking place. It contains redirection to XSUAA where he can specify user and password and then return to the page that was requested in the beginning.
Here are the steps Approuter executes during Login flow:
- User types address in url, for example https://my_approuter.cfapps.sap.hana.ondemand.com/index.html
- Request comes to Approuter, where we check whether authentication is required and if yes – check whether session in Approuter also exists
- In case there is no session – Approuter returns 200 to the Browser a tiny HTML page that is rendered in the Browser in case it is not an AJAX request. This HTML will contains several tags including location of XSUAA for client side redirect and cookie with information about the page that was primarily requested for example /index.html
- XSUAA redirects to IDP where login page is displayed
- User specifies user and password in login page and is redirected back to XSUAA
- XSUAA redirects back to Approuter and attaches authorization code to the URL
- Request arrives to Approuter and Approuter does the following:
- Sends authorization code it got to XSUAA Server-to-Server
- XSUAA responds with JWT token in case authorization code was valid
- Approuter creates session on its sides and stores JWT token in it
- Reads the cookie information about the page that was requested in the beginning
- Redirects to the primarily requested page for example /index.html
Fragment handling:
In case the main page that was requested has client-side navigation suffix (# signs),for example https://my_approuter.cfapps.sap.hana.ondemand.com/index.html#MyApp , Approuter will redirect to exact page after login, i.e. including specified page place (#MyApp).
Logout flow:
Approuter logout can take place in two main scenarios:
- Approuter session was timed out (Session timeout)
- User explicitly triggered logout flow (Client initiated)
Session timeout:
Session timeout can be modeled by configuring environment variable SESSION_TIMEOUT , the default value is 15 minutes.
Client initiated:
User can model logout section in xs-app.json and provide following properties:
- logoutEndpoint – user by going to this endpoint will trigger the central logout
- logoutPage – landing page that UAA will redirect to it after logout
,
"logout": {
"logoutEndpoint": "/my/logout",
"logoutPage": "http://employees.portal"
},
Here are the steps Approuter executes during Session timeout:
- Session timeout in memory store fires event when the session time has been passed
- Approuter deletes user session from session store
- Approuter requests all backend services logout paths
- Approuter requests all business services logout paths
Here are the steps Approuter executes during Client initiated logout:
- Approuter deletes user session from session store
- Approuter requests all backend services logout paths
- Approuter requests all business services logout paths
- Approuter redirects to logout endpoint of UAA and provides a modeled logoutPage as a redirect parameter
- UAA executes logout and removes the session on their level
- UAA verifies that logoutPage is in its modeled whitelist
- UAA redirects to logoutPage
Tip:
UAA will execute redirect only in case redirect URL is a valid redirect-uri in xs-security.json – redirect-uris are maintained as part of the oauth2-configuration section in the UAA application security descriptor JSON file given at the creation of the service instance. For example:
UAA application security descriptor xs-security.json:
"oauth2-configuration": {
"redirect-uris":
[
"http://employees.portal"
]
}
Tip: In case your logoutPage is internal (accessed via Approuter host) you should have a dedicated route for that. This route must be public route (authenticationType: none) because user lands on this page after logout process. Otherwise you will remain stuck with logout-login loop.
{
"source": "^/logout-page.html$",
"localDir": "openResources",
"authenticationType": "none"
}
Tip: In case your logoutPage is internal (accessed via Approuter host) and you want user can login again from it after, it is recommended to build your landing page with a link for example “Sign in again” and point it to your main page that must be protected path (authenticationType: xsuaa) or redirect to your main page after several seconds. In that way login flow will be triggered and a user will see in the browser login form of UAA. More details can be found in “Login flow“ section.
{
"source": "^/logout-page.html$",
"localDir": "openResources",
"authenticationType": "none"
},
{
"source": "^/ui/index.html",
"target": "index.html",
"localDir": "web",
"authenticationType": "xsuaa"
}
Tip: Be sure that your main route in your xs-app.json resource that matches the path is not cached by browser. Therefore, the best practice here would be to model “cacheControl” accordingly
{
"routes": [
{
"source": "^/ui/index.html",
"target": "index.html",
"localDir": "web",
"cacheControl": "no-cache, no-store, must-revalidate"
}
]
}
Storing application state in case of Session Timeout
Generally, Application should start connection with Approuter with a GET call. Approuter checks the request and in case the route is protected (requires authentication), there is no session for it and the HTTP method is not GET – Approuter will return status code 401. The same happens if there is no session and AJAX request comes to AppRouter. AppRouter treats a request as AJAX if it contains
the following header ‘x-requested-with’: ‘xmlhttprequest’ .
This use-case is very useful for applications.
There are cases in which session timeout takes place in the middle of user’s work. For example a user is filling some form in UI with several fields. Let’s say he made a break and at this time session was timed out. When user returns – he continues filling the form and presses “Save”. Approuter will send status 401 and it helps the application to understand that it needs to store the state. After state was saved the application can refresh the page (for example location.reload() ), that will trigger Approuter login flow, and then to fetch the stored state.
CSRF Protection
Application router exposes functionality of CSRF protection. You can achieve that by setting property “csrfProtection” with boolean value. The property itself is optional, default value is true. If you want to disable it – specify explicit “csrfProtection”:false on one particular route.
{
"source": "/employeeData/(.*)",
"target": "/services/employeeService/$1",
"destination": "employeeServices",
"csrfProtection": false
}
CSRF protection is relevant only for HTTP methods that make a side-effect on server (not HEAD and not GET).
It should works as following:
- Application gets status 403 Forbidden with response header x-csrf-token: Required from Approuter
- Application sends request with HTTP method HEAD or GET with header x-csrf-token: fetch
- Approuter generates x-csrf token and send it back in response header x-csrf-token: <token>
- Application sends again a request to Approuter but now with the header x-csrf-token: <token>
Tip: In case your backend application implement CSRF protection by itself, please make sure to switch it off in Approuter. In such case x-csrf token exchange will be executed directly between the client and the backend, without Approuter and it will work fine.
Tip: In case CSRF is enabled on Approuter side but it does not work – please verify you use it for protected route (“authenticationType”: “xsuaa”).
CORS
Cross-origin resource sharing (CORS) permits Web pages from other domains to make HTTP requests to your application domain, where normally such requests would automatically be refused by the Web browser’s security policy. Cross-origin resource sharing(CORS) is a mechanism that allows restricted resources on a webpage to be requested from another domain (/protocol/port) outside the domain (/protocol/port) from which the first resource was served. CORS configuration enables you to define details to control access to your application resource from other Web browsers.
Approuter has full support for Cross Origin and aligned with Apache Tomcat standard.
Approuter CORS flow works according to the flowchart below:
The Cross-Origin configuration is provided in the CORS environment variable.
The CORS configuration is an array of objects. Here are the properties that a CORS object can have: uriPattern, allowedOrigin, allowedMethods, maxAge, allowedHeaders, exposeHeaders, allowedCredentials.
Example:
[
{
"uriPattern": "^/route1$",
"allowedMethods": [
"GET"
],
"allowedOrigin": [
{
"host": "my_example.my_domain",
"protocol": "https",
"port": 345
}
],
"maxAge": 3600,
"allowedHeaders": [
"Authorization",
"Content-Type"
],
"exposeHeaders": [
"customHeader1",
"customHeader2"
],
"allowedCredentials": true
}
]
Tip: In case your backend application has CORS configuration on its side, please make sure not to configure CORS in Approuter. In such case CORS flow will be validated directly between the client and the backend, without Approuter and it will work fine.
Different Timeouts configuration in AppRouter:
There are many timeout configurations in AppRouter and it might be a little bit confusing. In this section I will go over them and will try make it more clear:
- AppRouter session timeout (environment variable SESSION_TIMEOUT) – positive integer number representing the AppRouter session timeout in minutes. This value represents number of minutes that “idle” AppRouter (that does not get any request) stores user session. Each time request arrives during this time – AppRouter extends the session again. The default value of this property is 15 minutes.
- XSUAA session timeout (cannot be configured) – limited in CF for 30 minutes. This the time that session on XSUAA is alive from the User made an authentication. After that time he will be redirected to login screen there he will need to authenticate again.
- Token validity (access_token_validity property in xs-security.json) – positive integer number representing the time in seconds after which issued JWT token will be expired.
- Incoming connection timeout (environment variable INCOMING_CONNECTION_TIMEOUT) – positive integer number in milliseconds representing the AppRouter session timeout in minutes. Maximum time in milliseconds for a client connection. After that time the connection is closed. If set to 0, the timeout is disabled. The default value 120000 (2 minutes).
- Destination timeout (property timeout and HTML5.Timeout in destination configuration) – positive integer number that represents maximum wait time for a response in milliseconds. It is relevant also for destinations logout requests. Keep in mind that requests from AppRouter to target destinations and services are synchronous. Also pay attention that maximum timeout in CF is limited to 2 minutes, therefore it does not make sense to define a value more than two minutes for this property. The default value is 30000 (30 seconds).
- Service timeout (property timeout in business service binding VCAP object configuration, inside endpoint section) – positive integer number that represents maximum wait time for a response in milliseconds. It is relevant also for business service logout requests. Keep in mind that requests from AppRouter to target destinations and services are synchronous. The default value is 30000 (30 seconds).
How to work with AppRouter in Multitenant mode:
AppRouter has a support for work multi-tenant mode. In order to achieve that, you need to create XSUAA service instance with mode “shared” (the configuration is in xs-security.json).
This XSUAA service instance should be bound to your AppRouter.
After that, create an environment variable TENANT_HOST_PATTERN for your AppRouter application. The value of this environment variable is Regular Expression that describes how tenant name should be retrieved from the host.
Example: ^(.*)-my_approuter_app.cfapps.sap.hana.ondemand.com
In the example above tenant name comes before approuter host and separated with “-“.
AppRouter validates during the start that whenever it is bound to xsuaa with mode “shared” – TENANT_HOST_PATTERN environment variable needs to be set.
Once AppRouter gets a request and recognizes that it does not have session for this particular uri – it retrieves tenant name by applying the tenant host pattern regex on incoming url.
After that when AppRouter calls XSUAA – it calls it with specific tenant url. For example,
my_tenant.<xsuaa_url>.
Then, XSUAA undertstands in scope of which tenant it it called and authentication flow works
according to this.
Note: tenant specific cf route should be mapped to AppRouter in order to make the flow work.
For productive usage “*” route should be configured and mapped.
For more information about multitenancy – please refer to https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/5e8a2b74e4f2442b8257c850ed912f48.html
Destinations and Integration with Destination Service:
Approuter can work with destination of two types:
- Destination defined as as environment variable on Approuter application
- Destination defined in Destination service
The first type is more basic and support simple authentication type. It does not require service destination . It is useful when the backend (destination) itself is in the same space and bound to the same XSUAA instance as the Approuter. This type of destination is configured with environment variable destinations . Changing one of the destinations requires restart of Approuter application.
Example:
[
{
"name" : "ui5",
"url" : "https://sapui5.netweaver.ondemand.com",
"proxyHost" : "proxy",
"proxyPort" : "8080",
"forwardAuthToken" : false,
"timeout" : 1200
}
]
This type of destinations as every environment variable can be provided in the manifest.yml .
Example:
- name: node-hello-world
memory: 100M
path: web
env:
destinations: >
[
{"name":"ui5", "url":"https://sapui5.netweaver.ondemand.com"}
]
Second type of destination requires binding of Approuter to service destination .
In such case one can specify destination details inside destination service, to create service instance of service destination and bound it to Approuter.
After that, in Runtime Approuter can dynamically call Destination service and get destination details from it.
Within such flexible integration with Destination service, user can benefit from:
- Consuming destinations that are defined on both Sub Account and Service Instance levels
- Consuming destination data relevant for this particular Consumer tenant
- Supporting all Authentication Types that Destination service exposes
- Ability to be redirected to changed destination data without restarting the Approuter
Integration with HTML5 App Repo service:
The application router supports seamless integration with the HTML5 Application Repository service. When the application router interacts with HTML5 Application Repository to serve HTML5 Applications, all static content and routes (xs-app.json) are retrieved from HTML5 Application Repository. In case application router needs to route to non HTML5 Applications, it is possible to model that in the xs-app.json of the application router.
To integrate HTML5 Application Repository to an application router based it is required to create an instance of html5-apps-repo service of plan app-runtime and bind it to the application. xs-app.json routes that are used to retrieve static content from HTML5 Application Repository may be modeled in the following format:
{
"source": "^(/.*)",
"target": "$1",
"service": "html5-apps-repo-rt",
"authenticationType": "xsuaa"
}
A valid request to application router that uses HTML5 Application Repository must have the following format:
https://<tenantId>.<appRouterHost>.<domain>/<bsPrefix>.<appName-appVersion>/<resourcePath>
bsPrefix (business service prefix) – Optional
- It should be used in case the application is provided by a business service bound to this approuter
appName (application name) – Mandatory
- Used to uniquely identify the application in HTML5 Application Repository persistence
- Must not contain dots or special characters
appVersion (application version) – Optional
- Used to uniquely identify a specific application version in HTML5 Application Repository persistence
- If no version provided, default application version will be used
resourcePath (path to file)
- The path to the file as it was stored in HTML5 Application Repository persistence
For more details please refer to the following Blog:
Programming applications in Sap Cloud Platform
Integration with Business Services:
Beside the backends of type destinations, Approuter is fully integrated with services.
Service can be authenticated with two types: client-credentials (technical user) and user authentication. In order to use service, the name of the service to which the incoming request should be forwarded should be modeled in route:
{
"source": "^/odata/v2/(.*)$",
"target": "$1",
"service": "com.sap.appbasic.country",
"endpoint": "countryservice"
}
Business Services expose specific information in VCAP_SERVICES credentials block that enable application router to serve Business Service UI and/or data.
Business Service data can be served using two grant types:
- User Token Grant: Application router performs a token exchange between login JWT token and Business Service token and uses it to trigger a request to the Business Service endpoint
- Client Credentials Grant: Application router generates a client_credentials token and uses it to trigger a request to the Business Service endpoint
While binding a Business Service instance to application router the following information should be provided in VCAP_SERVICES credentials:
- sap.cloud.service: Service name as referenced from xs-app.json route and business service prefix (if Business Service UI provided) – Mandatory
- sap.cloud.service.alias: Short service name alias for user friendly URL business service prefix- Optional
- endpoints: One or more endpoints that can be used to access Business Service data. If not provided url or uri attributes will be used – Optional
- html5-apps-repo: html5-apps-repo.app_host_id contains one or more html5-apps-repo service instance GUIDs that can be used to retrieve Business Service UIs – Optional
- saasregistryenabled: flag that indicates that this Business Service supports SaaS Registry subscription. If provided, application router will return this Business Service xsappname in SaaS Registry getDependencies callback – Optional
- grant_type: the grant type that should be used to trigger requests to the Business Service. Allowed values: user_token or client_credentials. Default value, in case this attribute is not provided, user_token – Optional
"country": [
{
...
"credentials": {
"sap.cloud.service": "com.sap.appbasic.country",
"sap.cloud.service.alias": "country",
"endpoints": {
"countryservice": "https://icf-countriesapp-test-service.cfapps.sap.hana.ondemand.com/odata/v2/countryservice",
"countryconfig": "https://icf-countriesapp-test-service.cfapps.sap.hana.ondemand.com/rest/v1/countryconfig"
},
"html5-apps-repo": {
"app_host_id": "1bd7c044-6cf4-4c5a-b904-2d3f44cd5569, 1cd7c044-6cf4-4c5a-b904-2d3f44cd54445"
},
"saasregistryenabled": true,
"grant_type": "user_token"
....
In order to support JWT token exchange, the login JWT token should contain the uaa.user scope. For that the xs-security configuration must contain a role template that references the uaa.user scope. For example:
{
"xsappname" : "simple-approuter",
"tenant-mode" : "shared",
"scopes": [
{
"name": "uaa.user",
"description": "UAA"
},
{
"name": "$XSAPPNAME.simple-approuter.admin",
"description": "Simple approuter administrator"
}
],
"role-templates": [
{
"name": "Token_Exchange",
"description": "UAA",
"scope-references": [
"uaa.user"
]
},
{
"name": "simple-approuter-admin",
"description": "Simple approuter administrator",
"scope-references": [
"$XSAPPNAME.simple-approuter.admin"
]
}
]
}
Conclusion
In this blog post I have shared with you main capabilities and features of SAP Application Router. After reading this you should be able to install, configure and start using it. However, it might be that you will have questions so in order to help you – I’ve already added some frequently asked questions and the answers for some of them as well. Please feel free to share and comment.
Troubleshooting and Frequently Asked Questions:
Q: Is session cookie name JSESSIONID constant?
A: Yes, this name is hard-coded in Cloud Foundry and is required for enabling session stickiness. Using this cookie GoRouter in CF knows to dispatch request to the same application instance.
Q: How to prevent collision of session cookies, for example if there are two Approuter applications with the same host but different ports (XSA) and the browser ignores ports
A: You can set environment variable USE_JSESSIONID_COOKIE_SUFFIX: 1 on Approuter. In such case Approuter will generate uniques session cookie name for each Approuter application. Be aware that session stickiness will not work in CF in case there is more than one CF instance, because CF Go Router knows expects hard coded name for it – JSESSIONID .
Q: How to make Approuter to return in http response additional headers, for example X-Frame-Option, Vary, Content-Type etc
A: You can achieve it by configuring environment variable httpHeaders . Please be aware that in case your backend already returns such headers, approuter cannot override its value.
For example:
httpHeaders : [{"X-Frame-Options":"ALLOW-FROM http://localhost"},{"Test-Additional-Header":"1"}]
Q: Can one run Approuter locally?
A: Yes. In order to do so, you should use two files in Approuter folder: default-env.json (that will contain environment variables you want to set to your Approuter project) and default-services.json (for CF and XSA, should contain VCAP information about bound services. Currently supports only xsuaa).
Q: Is it possible to use some service for authentication that does not have tag xsuaa?
A: Yes, you can define this service name with environment variable UAA_SERVICE_NAME
Q: Is it possible to check scopes inside backend destination?
A: Yes. You can set property forwardAuthToken with value true in destination definition (both in environment variable destinations and destination definition in destination service).
Q: How can we know which AppRouter version is running?
A: During AppRouter start – the version is printed in Log
Q: How can we see more detailed logs of AppRouter
A: You can execute following commands in CLI:
cf set-env <your_app> XS_APP_LOG_LEVEL DEBUG
cf set-env <your_app> REQUEST_TRACE true
cf restage
Q: What can we do if Approuter gets status 502 Connection Timeout during calling a destination
A: Basically, you can configure timeout property on destination (can be done in both destination that is defined in environment variable destinations and destination defined in destination service).
But keep in mind that AppRouter connection is synchronous, and CF limit for connection is 2 minutes.
Therefore, such configuration might be helpful only it takes less than 2 minutes.
For “slow destinations” – solution might be to have another design, asynchronous-like.
That means that you will have 2 endpoints:
– First one POST request for your heavy operation triggering, that will return 200 immediately.
– Second GET request that will make a polling each several seconds to check if the operation job was finished.
For that you will need to configure two different routes in xs-app.json.
Amazing article. Thank you so much for sharing all this.
Hi Zvi,
I have been working on XSA on-premise loading UI5 library’s and apps using Cross-MTA dependencies. This is working without issue when UAA is disabled, but fails when UAA is enabled on my target applications.
Instead of the resource being loaded, the following html is returned in it’s place:
<html><head><link rel=”shortcut icon” href=”data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7″ /><script>document.cookie=”fragmentAfterLogin=”+encodeURIComponent(location.hash)+”;path=/”;document.cookie=”locationAfterLogin=”+encodeURIComponent(location.href.split(‘#’)[0].split(location.host)[1])+”;path=/”;document.cookie=”signature=SS6p9Zjvm68tnTr0cos3BNf0phE%3D;path=/”;location=”https://uaa-server.sap-xxx-db.xxxxx.xxx.xx:33033/uaa-security/oauth/authorize?response_type=code&client_id=sb-launchpaddemoapp-xxx!i2&redirect_uri=https%3A%2F%2Fxxxx-xxx-launchpaddemo-launchpaddemo.sap-xxx-db.xxxxx.xxx.xx%3A33033%2Flaunchpaddemoap%2Flogin%2Fcallback”</script></head></html>
I have since avoided this situation by moving away from using the approuter, but am wondering if this is a limitation of the @sap/approuter, or if there is a specific configuration that will allow the token to be processed correctly?
Would you be able to take a quick look over my scenario please? It would be great to get your opinion on whether this type of setup is viable? Additional detail can be found here: https://answers.sap.com/questions/13014597/xsa-cross-mta-dependency-with-uaa.html
Thank you,
Bradley.
Hi Bradley,
This html that you mention - is part of login flow (please refer to section "Login flow", bullet # 3). As you can see it has "location" element with UAA's url value. When this html is processed by Browser - client side redirect to UAA should take place.
You can try track the login flow in Chrome Dev Tool and try to understand why redirect does not happen .
Best Regards,
Zvi
Hi Zvi,
thanks for this blog, the clearifies several things which puzzled me!
You shoud add a link or at least mention that the approuter has a README.md, although it is carefully hidden as there is no public source code repo.... Thankfully Gregor Wolf has provided us with https://github.com/gregorwolf/SAP-NPM-API-collection/tree/master/apis/approuter .
Also I would like to react to the session timeout with an iframe overlay with the relogin, as eg launchpad.support.sap.com does. I would like to see some sample code for this (of course this assumes an IdP which alllows display in an iframe..), cf https://github.com/SAP/openui5/issues/958 .
Also more general to the question of Bradley Smith , how does working with more than one html5 repo really work, eg. one with the apps, one with custom libs?
Regards,
Wolfgang
Hi Wolfgang Röckelein,
Just a quick heads up. You can download any of these node modules locally. https://help.sap.com/viewer/4505d0bdaf4948449b7f7379d24d0f0d/2.0.02/en-US/726e5d41462c4eb29eaa6cc83ff41e84.html
You may need to use the -f flag to force if it isn't playing nice.
Thanks,
Bradley.
Hi Bradley Smith ,
yes I know, yet it is more convenient to access the READMEs like I described.
Also because of this easy download, I would not consider anything downloadable in this way as "not known outside".
Regards,
Wolfgang
Nor would I consider it as “not known outside”.
Hi Wolfgang,
Unfortunately, README file is treated as an internal SAP source, therefore is not known outside.
Please also be informed that there is a public documentation of SAP AppRouter:
https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/01c5f9ba7d6847aaaf069d153b981b51.html
Regarding you iframe scenario - unfortunately, I am not familiar with UI5 - you should consult with somebody who knows. Only one thing that I am not sure that can help - if you are "struggling" with timeout of AppRouter session and you would like to avoid it - consider using "keep alive" pattern. I mean - add some simple route in xs-app.json that does nothing and try to access it from client for example each 10 minutes. It will extend AppRouter session each time.
Did you also have a question about integration with html5 apps repo service? In case yes - please elaborate your question. You can also refer to another blog regarding it - the link is inside teh relevant section in my blog.
Regards,
Zvi
Readme is visible since Approuter is now available in npmjs.com:
https://www.npmjs.com/package/@sap/approuter
Just the blog I was looking for. It is really informative and helped me a lot with my current project.
Thanks for sharing it.
Hi Zvi Zeltser ,
Thank you for sharing wonderful blog. I tried CORS for my UI5 app but unfortunately app has stopped working. Will you be able to point out the issue here. This is my xs-app.json file
The standard https port is 443.
Hi,
CORS configuration should not be part of xs-app.json but an environment variable on your AppRouter application.
Best Regards,
Zvi
Hi Zvi,
Thank you for your response. I will try environment variable. I have bookmarked this blog, and shared the link with all the developers. I think everyone should read this.
Thanks
Pranav
Hi Zvi Zeltser ,
I am still not able to work on the CORS. Do you have example how it can be done?
Thanks
Pranav
Hi Pranav,
You can refer to this section in oficial AppRouter documentation:
"https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/ba527058dc4d423a9e0a69ecc67f4593.html"
Regards,
Zvi
Hi Zvi Zeltser ,
Thanks. cf set-env works and I was able to open the UI5 in iFrame. In addition to CORS, I was required to change "SEND_XFRAMEOPTIONS" for UI5 to work in iFrame.
To avoid doing cf set-env in non-development environment, I tried to handle CORS in mta.yml but that didn't work. Well, I will try it again.
Thanks
Pranav
Zvi, this is brilliant material. Can we get it integrated into the core Approuter documentation?
Hello Zvi! Amazing blog.. It's helping me a lot!
I've a doubt that maybe you can solve me.. I've developed one sample app consuming an SCP destination and is working fine running locally because I'm using ui5.yaml configuration to map the destination but when I deploy my MTAR archive to CF I've a problem with the approuter, the destination url returns 404 Not Found, it seems is cause of bad configuration in xs-app.json. My xs-app.json is this:
When I run the application after deploy it, the following url is launched to consume service and causes error:
If I test this url deleting /uimodule works fine! By this I think the problem is in my route configuration in xs-app.json, it seems is trying to execute destination inside uimodule path and not in main path..
Could you pleas help me with this?
Thanks in advance!!
Hi,
If you want url "
" will match your second route in xs-app.json, your source there should be something like "source": "^/uimodule/ABAPdest..."
And you should swap the routes in xs-app.json, because it takes the first matching route. I believe it will solve your issue.
Regards,
Zvi
Hi Jorge,
I am also facing similar issue. Did your issue resolve after applying changes which Zvi Zelter suggested below ?
Best Regards,
Anuj
Hi Zvi,
Awesome Blog post!
I want to use destination service in my local project which has node.js module and SAPUI5 module. I Created service key in destination service and using it i created user provided service with tag destination. It is working fine for Node modules but, for sapui5 module it is using approuter and somehow approuter is not fetching the details from user provided service.
Do you know anything we could do here which can help approuter to use destination service using user provided service?
Thanks,
Rajdeep Bhuva
Hi All,
I achieved this requirement by manually loading default-services.json file while starting approuter.
Thanks,
Rajdeep Bhuva
Hi,
How to configure a route in xsapp.json for just authentication with userId and password (but no scope check from xsuaa)?
Cloud Foundry doesn't support support basic authentication. Why would you need it? If you want to provide access to an API check out this great blogpost: How to call protected app from external app as external user with scope from Carlos Roggan.
Hi Gregor,
I have a specific use case. Our application has exposed an endpoint which shows the documentation of our application APIs and we want this endpoint to be accessible to anyone within SAP. Hence, I wish to have a basic authentication of SAP userId and password for this endpoint. Any idea on how can we achieve it? (Using roles/scopes is not in the picture as it would need a client to hit our endpoint but we want it to be accessed by normal browser)
Kush.
Hi Kush,
so users should access the API documentaiton endpoint? Just use the normal authentication which is configured using SAP ID.
Best regards
Gregor
Yes, we wish to share the api endpoint with multiple teams (and do not wish to share with outside world). How can I configure this on my endpoint with the sap id?
If you want to limit it to SAP Employees I think you have to get in touch with the Administrators of the SAP ID service that you get a Group assertion provided which you can use to restrict the access. You need to activate XSUAA for your deployed app.
Hi Gregor Wolf ,
I have a requirement to add Product ID(or any other dynamic attribute) to each request routed to API through HTML5 Application Repository UI App. We want to add this through App Router and not through UI because of some security reason. Is there a way to do it through xs-app.json config or any other way without customizing the app router?
Thanks,
Parth
Dear Parth,
what is the source of the dynamic attribute?
Best regards
Gregor
Hi Gregor,
It's a Business Partner ID associated with every user. I do a first call to get BP id for user and than for each subsequent call in the session, that BP ID should be appended to the request.
Thanks,
Parth
Can't your backend API do the same check based on the User ID that you're getting? If you do not control the backend API you have to implement some middleware. I would suggest to use CAP for that. Check out my custom handler implementation in customer-material-service.js.
Hi Gregor,
Yes it's a external system and can't control it. Middleware does makes sense but the problem is batch calls are being made and it costs a huge performance issue to parse the batch body. Plus, for every call I will have to first get the BP and than process actual request. Just to avoid this wanted to see if router can instead do it for me.
Thanks,
Parth
Awesome blog post! Thank you!
Hi Zvi,
How can we implement custom routes in approuter like we create using express.js and how to maintain it in xs-app.json.
Thanks,
Rajdeep Bhuva
Hi,
have a look into extending.md inside package docs.
There you will find examples how to extend approuter using custom middleware.
Best Regards
Holger
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?
Maybe you can set the VCAP_SERVICES environment variable before you start your backend service locally. When you provide the same XSUAA information as for the approuter you should be fine. The benefit of JWT is that they can be validated without a call to the XSUAA. The public key of the XSUAA is used to verify the signature of the JWT.
Thanks it worked. Also, I was able to make it work with out VCAP env variables but configuring xsuaa clientid, uaadomian etc. in application yaml file.
Can you describe this approach mit application yaml file in more detail? Be aware that you never should commit such files to a Git repository.
I just described these in my yml file. Of course these are for local testing and not planning to commit .Thanks.
Great article! , one doubt though!
Assuming a local instance of App router.
How does session stickiness work with AppRouter , does it support multiple back end instances of the same service and balance requests to them considering stickiness?
Great article.
However I am facing a weird problem, serving a locally running app router inside an iframe doesn't work, says http://localhost:5000/login/callback?code=XXXXXXX signature does not match but the deployed version in Cloud Foundry works.
Any idea how to fix this?
Have you provided the needed data in a default-env.json file?
Hi Gregor,
Here is my default env
I have also ensured that uaa domain can run in an iframe.
It works perfectly fine in production but in local development it does not work for some reason inside an iframe but standalone it works.
Hello Abhijith,
have you found a solution?
I have the same problem. Thought that
"strictSSL": false should do the trick. But unfortunately not for me.
Any idea?
Best Regards, Dirk
Hi Dirk,
When Single Sign on (Trust with sap idp) is enabled it won’t work because the xsuaa domain needs to have whitelisted urls to be rendered in an iframe (localhost port was added to whitelist) and localhost fails to do a successful redirect.
but when deployed to cloudfoundry it works fine.
For our use case we didn’t need to run the portal application locally as the microfrontends were isolated and could be developed individually. So just running the microfrontend with app router and single sign on worked for us.
Setting the "authenticationMethod" from "xsuaa" to "none" (xs-app.json) is the only working solution for the moment. (Its a XSA system behind a firewall.)
Issue:
Setting "strictSSL": false and the proxy properties doesn't work for me.
If someone has a better solution. Please let me know!
default-env.json (this is what I've tried)
Hi Zvi,
Thanks for a very helpful blog.
can you please also share if app-router can also be used as load balancer?
if yes , how to configure that aspect?
if not, any hint how can we introduce load balancing?
Thanks in advance.
Best Regards,
Apoorv
Regarding session timeout handling. Does it mean that, in case I need to make sure that the approuter returns a 401, even when it's a GET request, I need to add a x-requested-with’: ‘xmlhttprequest’ header to the request?
Turns out this was easy to test and confirm myself.
In Postman I execute a GET call to an approuter uaa protected endpoint:
Then I add the X-Requested-With header containing the value xmlhttprequest and I receive a 401
I think this is very important information that should be included in the approuter README and I also think this should be a default header for UI5 OData (v2) read requests (in case batch is disabled).
Pieter
Hi,
is there any HTTP/2 support in @sap/approuter?
If not, do you plan to support HTTP/2 in future? If so, when?
Rudolf
Hi ,
How we can redirect logout page automatically incase of session time out?
Thanks,
This is my landscape.
I have an OData service that runs on the SAP GTT environment and I am able to consume this service in a SAPUI5 application runs in the NEO environment by defining a destination with BasicAuth type and my Technical username and password. Also, I can consume this service in the Postman again with the same authentication way.
This is the service URL;
https://emea.gtt-flp-lbnplatform.cfapps.eu10.hana.ondemand.com/api/outbound/odata/v1/com.emea.gtt.app.gttft1.gttft1Service
I developed and deployed a SAPUI5 for SAP GTT environment by using xs-app.json routing and I created the same destination inside this environment but I always receive a 401-Unauthorized error as the application request the metadata file. You can find my application configuration below.
Where I am doing wrong with this way? At the end of the day, I want to run this application in the same environment which is running that Odata service.
I also tried the "basic" parameter in xs-app.json but it also didn't.
The second point, even if I define this destination in my personal trial CF account in order to access it from another environment, again I can not consume this service.
Thanks in advance.
Hi Zvi Zeltser,
currently I have specified the routing in a static way inside of xs-app.json:
Is there any way to provide the destination value programmatically/dynamically inside of routes?
Thanks.
Believe so, although I haven't done it.
You need to extend the approuter and provide a custom configuration. The trick is function getRouterConfig which is called by the approuter on every request.
Have a read on the documentation in file "node_modules/@sap/approuter/doc/extending.md" after you npm install the approuter.
Here's an excerpt from the documentation
Tiago Almeida, thanks for sharing the link to the reference.
I've tried mentioned approach with a dynamic routing and moved the static routing from xs-app,json:
to the app.js:
Deployed the app to the CF, run it and get "File not found", 404. With a static routing configuration everything is OK.
The app.js is executed via the npm-script:
Tiago Almeida, I've checked the case deeper, and paid attention that my custom approuter configuration via app.js is not used at all by CF. Only configuration within xs-app.json is respected.
Is there any way to set which configurations should be followed one from xs-app.json or one from app.js provided by the custom code?
Thanks.