@sap/approuter ============== - [Overview](#overview) - [Deploying a business application with microservices](#deploying-a-business-application-with-microservices) - [Working directory](#working-directory) - [Configurations](#configurations) * [Destinations](#destinations) + [Environment-destinations](#environment-destinations) + [Destination-service](#destination-service) * [UAA configuration](#uaa-configuration) * [Additional headers configuration](#additional-headers-configuration) * [Additional cookies configuration](#additional-cookies-configuration) * [Plugins configuration](#plugins-configuration) * [Session timeout configuration](#session-timeout-configuration) * [X-Frame-Options configuration](#x-frame-options-configuration) * [Cross-Origin Resource Sharing configuration](#cross-origin-resource-sharing-configuration) * [Direct Routing URI Patterns configuration](#direct-routing-uri-patterns-configuration) * [NodeJS Minimal Logging Level configuration](#nodejs-minimal-logging-level-configuration) * [Dynamic Identity Provider configuration](#dynamic-identity-provider-configuration) - [Routes](#routes) * [Example routes](#example-routes) - [Replacements](#replacements) - [*xs-app.json* configuration file](#xs-appjson-configuration-file) * [*welcomeFile* property](#welcomefile-property) * [*authenticationMethod* property](#authenticationmethod-property) * [*routes* property](#routes-property) * [*login* property](#login-property) * [*logout* property](#logout-property) * [*destinations* property](#destinations-property) * [*services* property](#services-property) * [*responseHeaders* property](#responseHeaders-property) * [*compression* property](#compression-property) * [*pluginMetadataEndpoint* property](#pluginmetadataendpoint-property) * [*whitelistService* property](#whitelistservice-property) * [*websockets* property](#websockets-property) * [*errorPage* property](#errorpage-property) * [*cors* property](#cors-property) * [Complete example of an *xs-app.json* configuration file](#complete-example-of-an-xs-appjson-configuration-file) - [Headers](#headers) * [Forwarding Headers](#forwarding-headers) * [Hop-by-hop Headers](#hop-by-hop-headers) * [Custom Headers](#custom-headers) * [Authorization Header](#authorization-header) - [CSRF Protection](#csrf-protection) - [Support of SAP Statistics](#support-of-sap-statistics) - [Connectivity](#connectivity) - [SaaS Application Registration in CF](#saas-application-registration-in-cloud-foundry) * [How To Expose Approuter for SaaS Subscription](#how-to-expose-approuter-for-saas-subscription) - [Authentication with Identity Service (IAS)](#authentication-with-identity-service-ias) - [Mutual TLS Authentication (mTLS) and Certificates Handling](#mutual-tls-authentication-mtls-and-certificates-handling) - [Integration with HTML5 Application Repository](#integration-with-html5-application-repository) - [Integration with Business Services](#integration-with-business-services) - [Web Sockets](#web-sockets) - [Session Handling](#session-handling) * [Session Contents](#session-contents) - [External Session Management](#external-session-management) - [Service to Application Router](#service-to-application-router) - [Central Logout](#central-logout) - [User API Service](#user-api-service) - [Allowlist Service](#whitelist-service) * [Enable the service](#enable-the-service) * [Configuring allowed hostnames / domains](#configuring-allowed-hostnames--domains) * [Return value](#return-value) - [Scaling](#scaling) - [Sizing Guide for Application Router](#sizing-guide-for-application-router) - [Configure server-side HTTPS](#configure-server-side-https) - [Audit-Log Service](#audit-log-service) - [Troubleshooting](#troubleshooting) - [Getting Support](#getting-support) - [Extending Application Router](#extending-application-router) - [Best practices](#best-practices) * [Security best practices](#security-best-practices) + [Content-Security-Policy](#content-security-policy) ## Overview When a business application consists of several different apps (microservices), the application router is used to provide a single entry point to that business application. It has the responsibility to: * Dispatch requests to backend microservices (reverse proxy) * Authenticate users * Serve static content ![Application router overview diagram](doc/app-router-diagram.png "Overview diagram") Let's think of the different apps (microservices) as _destinations_ to which the incoming request will be forwarded. The rules that determine which request should be forwarded to which destination are called _routes_. For every destination there can be more than one route. You may read more on the concept of [routes](#routes) later in this document. If the backend microservices require authentication, the application router can be configured to authenticate the users and propagate the user information. Again by using routes, the application router can serve static content. The application router is designed to work in XS Advanced - Cloud Foundry and XS OnPremise Runtime. A calling component accesses a target service by means of the application router only if there is no JWT token available, for example, if a user invokes the application from a Web browser. If a JWT token is already available, for example, because the user has already been authenticated, or the calling component uses a JWT token for its own OAuth client, the calling component calls the target service directly; it does not need to use the application router. **Note** that the application router does not hide the backend microservices in any way. They are still directly accessible bypassing the application router. So the backend microservices _must_ protect all their endpoints by validating the JWT token and implementing proper scope checks. Network isolation is not provided currently by the platform. ## Deploying a business application with microservices For example we can have a business application that has the following structure:
+-- manifest.yml
+-- manifest-op.yml
|  +-- microservice-1
|  | +-- ...
|  | +-- ...
|  +-- microservice-2
|  | +-- ...
|  | +-- ...
|  +-- web
|  | +-- ...
|  | +-- ...
The *manifest.yml* file is used to deploy the business application on Cloud Foundry and the *manifest-op.yml* - on the XS OnPremise Runtime. These files should describe all the microservices for that business application. Folders are used to isolate the different microservices. Let's assume that the application router is the microservice in the *web* folder (every business application has its own application router). Here is how we can include the application router: * Manually create the *node_modules* folder in the *web* folder. * Copy and paste the folder that contains the self-contained application router into *node_modules*. In this example the name of that folder is *@sap/approuter*, see the *start* script in the *package.json* below. * Check the version of the application router you just copied. * Create a *package.json* file in *web* with content similar to the following and replace the version's value with the version of your application router: ```json { "name": "hello-world-approuter", "dependencies": { "@sap/approuter": "2.6.1" }, "scripts": { "start": "node node_modules/@sap/approuter/approuter.js" } } ``` In order to use the application router you don't have to write any JavaScript code. Only some configurations have to be provided in the *web* folder. Here is a complete example:
+-- web
| +-- package.json
| +-- xs-app.json
| +-- resources
| | +-- hello-world.html
| | +-- my-page.html
| +-- node_modules
| |   +-- ...
| +-- default-env.json
| +-- default-services.json
The *web* folder contains the *package.json*, *node_modules*, some configuration files used by the application router, and static resources to be served. You can read more about the [configurations](#configurations) later in this document. By default, the application router runs on port 5000 (if started locally) or it takes the port from the `PORT` environment variable. ## Working directory The working directory contains configuration files that the application router needs and static resources that can be served at runtime. In the [previous example](#deploying-a-business-application-with-microservices), the *web* folder is the working directory. By default the current directory is the working directory. It is possible to configure it during start up of the application router with the following command line argument: ```bash node approuter.js -w ``` Application router will abort if the working directory does not contain *xs-app.json* file. ## Configurations The application router makes use of the following configurations: - [Main configuration](#xs-appjson-configuration-file) - this is the *xs-app.json* file. This file is mandatory and contains the main configurations of the application router. - [UAA configuration](#uaa-configuration) - the application router reads this configuration either from the `VCAP_SERVICES` environment variable (when deployed on Cloud Foundry or XS Advanced OnPremise Runtime) or from the *default-services.json* file (when running locally). Refer to the documentation of the `@sap/xsenv` package for more details. - Configurations from the environment - these configurations are either read from the application router's environment (when deployed on Cloud Foundry or XS Advanced OnPremise Runtime) or from the *default-env.json* file (when running locally). Refer to the documentation of the `@sap/xsenv` package for more details. The environment variables that the application router takes into account are: Configuration | Environment variable | Description ------------- |---------------------------------------| ------------ [UAA service name](#uaa-configuration) | `UAA_SERVICE_NAME` | Contains the name of the UAA service to be used. [Destinations](#destinations) | `destinations` | Provides information about the available destinations. [Additional headers](#additional-headers-configuration) | `httpHeaders` | Provides headers that the application router will return to the client in its responses. [Additional cookies](#additional-cookies-configuration) | `COOKIES` | Provides cookies that the application router will return to the client in its responses. Currently only SameSite cookie is supported. [Plugins](#plugins-configuration) | `plugins` | A plugin is just like a [*route*](#routes) except that you can't configure some inner properties. [Session timeout](#session-timeout-configuration) | `SESSION_TIMEOUT` | Positive integer representing the session timeout in minutes. The default timeout is 15 minutes. [X-Frame-Options](#x-frame-options-configuration) | `SEND_XFRAMEOPTIONS`, `httpHeaders` | Configuration for the X-Frame-Options header value. [Allowlist service](#whitelist-service) | `CJ_PROTECT_WHITELIST` | Configuration for the allowlist that is preventing clickjack attacks. [Web Sockets origins allowlist](#web-sockets) | `WS_ALLOWED_ORIGINS` | An allowlist configuration that is used for verifying the `Origin` header of the initial upgrade request when establishing a web socket connection. JWT Token refresh | `JWT_REFRESH` | The time in minutes before a JWT token expires and the application router should trigger a token refresh routine. Incoming connection timeout | `INCOMING_CONNECTION_TIMEOUT` | Maximum time in milliseconds for a client connection. After that time the connection is closed. If set to 0, the timeout is disabled. Default: 120000 (2 min) Tenant host pattern | `TENANT_HOST_PATTERN` | String containing a regular expression with a capturing group. The request host is matched against this regular expression. The value of the first capturing group is used as tenant id. Destination host pattern | `DESTINATION_HOST_PATTERN` | String containing a regular expression with a capturing group. The request host is matched against this regular expression. The value of the capturing group is used as destination name. [Compression](#compression-property) | `COMPRESSION` | Configuration regarding compressing resources before responding to the client. _Secure_ flag of session cookie | `SECURE_SESSION_COOKIE` | Can be set to `true` or `false`. By default, the _Secure_ flag of the session cookie is set depending on the environment the application router runs in. For example, when application router is behind a router (Cloud Foundry's router or SAP Web Dispatcher) that is configured to serve HTTPS traffic, then this flag will be present. During local development the flag is not set. This environment variable can be used to enforce setting or omitting the _Secure_ flag. **Note**: If the Secure flag is enforced, the application router will reject requests sent over unencrypted connection (http). Trusted CA certificates | `XS_CACERT_PATH` | List of files paths with trusted CA certificates used for outbound https connections (UAA, destinations, etc.). File paths are separated by [path.delimiter](https://nodejs.org/api/path.html#path_path_delimiter). If this is omitted, several well known "root" CAs (like VeriSign) will be used. This variable is set automatically by XSA On-premise runtime. Reject untrusted certificates | `NODE_TLS_REJECT_UNAUTHORIZED` | By default an outbound https connection is terminated if the remote end does not provide a trusted certificate. This check can be disabled by setting `NODE_TLS_REJECT_UNAUTHORIZED` to `0`. This is a built-in feature of Node.js. **Note:** Do not use this in production as it compromises security! External reverse proxy flag | `EXTERNAL_REVERSE_PROXY` | Boolean value that indicates the use of application router behind an external reverse proxy (outside of Cloud Foundry domain) Skip client credentials tokens load on start | `SKIP_CLIENT_CREDENTIALS_TOKENS_LOAD` | Boolean value that indicates that no client credentials tokens should be created during the application router start phase [Cross-Origin Resource Sharing](#cross-origin-resource-sharing-configuration) | `CORS` | Configuration regarding CORS enablement. Preserve URL fragment | `PRESERVE_FRAGMENT` | When set to `true` or not set, fragment part of the URL provided during first request of not logged-in user to protected route will be preserved, and after login flow user is redirected to original URL including fragment part. However, this may break programmatic access to Approuter (e.g. e2e tests), since it introduces change in login flow, which is incompatible with Approuter version 4.0.1 and earlier. Setting value to `false` makes login flow backward compatible, however will not take fragment part of the URL into account. [Direct Routing URI Patterns](#direct-routing-uri-patterns-configuration) | `DIRECT_ROUTING_URI_PATTERNS` | Configuration for direct routing URI patterns. [NodeJS Minimal Logging Level](#nodejs-minimal-logging-level-configuration) | `CF_NODEJS_LOGGING_LEVEL` | Configuration for NodeJS minimal logging level. [Dynamic Identity Provider](#dynamic-identity-provider-configuration) | `DYNAMIC_IDENTITY_PROVIDER` | Configuration for dynamic identity provider. Backend Cookies Secret | `BACKEND_COOKIES_SECRET` | Secret that is used to encrypt backend session cookies in service to Application Router flow. Should be set in case multiple instances of Application Router are used. By default a random sequence of characters is used. Service to Application Router | `SERVICE_2_APPROUTER` | If `true`, when the SAP Passport header is received from the application router, it will be transferred without modification to the backend application. Client certificate header name | `CLIENT_CERTIFICATE_HEADER_NAME` | When set application router will use this header name to get the client certificate from the request header in subscription callback. If not provided the default header name `x-forwarded-client-cert` is used. Server Keep Alive | `SERVER_KEEP_ALIVE` | server keep alive timeout (positive integer in milliseconds). Minimum Token Validity | `MINIMUM_TOKEN_VALIDITY` | positive integer in seconds. When set, approuter will check that the token returned from the authorization service has an expiration time higher than the minimum token validity value. State Parameter Secret | `STATE_PARAMETER_SECRET` | enables the use of state parameters to prevent CSRF attacks. If this environment variable is set, the application router creates a state parameter for each initial authorization request. By validating that the authentication server returns the same state parameter in its response, the application server can verify that the response did not originate from a third party. **Note**: this feature is only available in Cloud Foundry runtime HTTP2 Support | `HTTP2_SUPPORT` | Enables the application router to start as an HTTP/2 server. Note: To configure HTTP/2 support, you must use Cloud Foundry routes with an HTTP/2 destination protocol. See [Configuring HTTP/2 Support](https://docs.cloudfoundry.org/adminguide/supporting-http2.html#application) in the Cloud Foundry Documentation. As connection-specific header fields aren't supported by the HTTP/2 protocol, see [rfc9113](https://datatracker.ietf.org/doc/html/rfc9113), the application router removes such headers automatically when they are returned from a backend to prevent a failure of the HTTP/2 response. Store CSRF token in external session | SVC2AR_STORE_CSRF_IN_EXTERNAL_SESSION | If `true` and have enabled [external session management](#external-session-management), the application router can generate and validate CSRF tokens in service-to-application-router flows by storing the token in an external session. Cache service credentials | CACHE_SERVICE_CREDENTIALS | If `true`, services credentials are cached in the application router memory Enable x-forwarded-host header validation | ENABLE_X_FORWARDED_HOST_VALIDATION | If `true`, x-forwarded-host validation will be performed, allowing letters, digits, hypens (-), underscores (_) and dots (.). As well as it validates hostname length. Add the content security policy headers to the response |ENABLE_FRAME_ANCESTORS_CSP_HEADERS | If `true`, Approuter will include the content security policy (CSP) header using subaccount trusted domains with frame-ancestors policy. **Note:** all those environment variables are optional. ### Destinations The destinations configuration can be provided by the `destinations` environment variable or by destination service. There has to be a destination for every single app (microservice) that is a part of the business application. #### Environment destinations The destinations configuration is an array of objects. Here are the properties that a destination can have: Property | Type | Optional | Description -------- | ---- |:--------:| ----------- name | String | | A unique alphanumeric identifier of the destination. url | String | | URL of the app (microservice). proxyHost | String | x | The host of the proxy server used in case the request should go through a proxy to reach the destination. proxyPort | String | x | The port of the proxy server used in case the request should go through a proxy to reach the destination. forwardAuthToken | Boolean | x | If `true`, the OAuth token is sent to the destination. The default value is `false`. This token contains user identity, scopes and other attributes. It is signed by the UAA or IAS service, so it can be used for user authentication and authorization with backend services. forwardAuthCertificates | Boolean | x | If `true`, the certificates and key of the authentication service are added to the HTTP connection to the destination. The default value is false. For more information see: [Mutual TLS Authentication (mTLS) and Certificates Handling](#mutual-tls-authentication-mtls-and-certificates-handling). strictSSL | Boolean | x | Configures whether the application router should reject untrusted certificates. The default value is `true`.
**Note:** Do not use this in production as it compromises security! timeout | Number | x | Positive integer representing the maximum wait time for a response (in milliseconds) from the destination. Default is 30000ms. setXForwardedHeaders | Boolean | x | If `true` , the application router adds X-Forwarded-(Host, Path, Proto) headers to the backend request.Default value is true. proxyType | String | x | Configures whether the destination is used to access applications in on-premise networks or on public Internet. Possible value: `OnPremise`. if the property is not provided, it is assumed that it is a public Internet access.
**Note:** if `OnPremise` value is set, binding to SAP Cloud Platform connectivity service is required, and `forwardAuthToken` property should not be set. IASDependencyName | String | x | Configures the name of the IAS dependency that is used to exchange the IAS login token. The exchanged token is also forwarded to the backend application. **Note:** The timeout specified will also apply to the [destination's logout path](#destinations-property) or [service's logout path](#services-property) (if you have set one).
**Note:** `proxyHost` and `proxyPort` are optional, but if one of them is defined, then the other one becomes mandatory. Sample content of the destinations environment variable: ```json [ { "name" : "ui5", "url" : "https://ui5.sap.com", "proxyHost" : "proxy", "proxyPort" : "8080", "forwardAuthToken" : false, "timeout" : 1200 } ] ``` It is also possible to include the destinations in the *manifest.yml* and *manifest-op.yml* files: ```yml - name: node-hello-world memory: 100M path: web env: destinations: > [ {"name":"ui5", "url":"https://ui5.sap.com"} ] ``` #### Destination service Destination configuration can also be read from `destination service` .
Here are the Approuter limitations to destination properties configuration from destination service : Property | Additional Property | Description -------- |:--------:| ----------- Type | |only `HTTP` supported. Authentication | | All authentication types are supported.
**Note:** `User` and `Password` are mandatory if the authentication type is `basic authentication`.
**Note:** if the authentication type set to `principal propagation` the ProxyType have to be `on-premise`.
**Note:** if the authentication type set to `OAuth2SAMLBearerAssertion`, `uaa.user` scope in xs-security.json is required. ProxyType | | Supported proxy type : `on-premise`, `internet`, `private-link`.
**Note:** if ProxyType set to `on-premise`, binding to SAP Cloud Platform connectivity service is required.
**Note:** To check the availability of the private-link proxy type, see [SAP Private Link ServiceInformation](https://discovery-center.cloud.sap/serviceCatalog/private-link-service?tab=service_plan®ion=all&service_plan=standard&commercialModel=cpea) published on SAP site in the SAP Discovery Center. ##### Optional additional properties: Property | Additional Property | Description -------- |:-------------------:| --------- HTML5.ForwardAuthToken | x | If `true` the OAuth token will be sent to the destination. The default value is `false`. This token contains user identity, scopes and other attributes. It is signed by the UAA so it can be used for user authentication and authorization with backend services.
**Note:** if ProxyType set to `on-premise`, ForwardAuthToken property should not be set.
**Note:** if Authentication type is other than NoAuthentication, ForwardAuthToken property should not be set. HTML5.ForwardAuthCertificates | x | If `true`, the certificates and key of the authentication service are added to the HTTP connection to the destination. The default value is false. For more information see: [Mutual TLS Authentication (mTLS) and Certificates Handling](#mutual-tls-authentication-mtls-and-certificates-handling). HTML5.Timeout | x | Positive integer representing the maximum wait time for a response (in milliseconds) from the destination. Default is 30000ms.**Note:** The timeout specified will also apply to the [destination's logout path](#destinations-property) or [service's logout path](#services-property) (if you have set one). HTML5.PreserveHostHeader | x | If `true` , the application router preserves the host header in the backend request.
This is expected by some back-end systems like AS ABAP, which do not process x-forwarded-* headers. HTML5.DynamicDestination | x | If `true` , the application router allows to use this destination dynamically on host or path level. HTML5.SetXForwardedHeaders | x | If `true` , the application router adds X-Forwarded-(Host, Path, Proto) headers to the backend request.Default value is true. HTML5.IASDependencyName | x | Configures the name of the IAS dependency that is used to exchange the IAS login token. The exchanged token is also forwarded to the backend application. sap-client | x | If provided, the application router propagates the sap-client and its value as a header in the backend request.
This is expected by ABAP back-end systems. URL.headers.`` | x | If provided, the application router propagates this special attribute in the destination as the header. The application router can get the headers list from the destination API. Existing request headers are not overwritten.
**Note:** * In case destination with the same name is defined both in environment destination and destination service, the destination configuration will load from the environment. * Destinations on destination service instance level are supported. * Only destination client certificates of type p12 are supported. * Only destination trust certificates of the type privacy-enhanced mail (PEM) are supported. ### UAA configuration The User Account and Authentication (UAA) server is responsible for user authentication. In Cloud Foundry and XS OnPremise Runtime a service is created for this configuration and by using the standard service binding mechanism the content of this configuration is available in the `VCAP_SERVICES` environment variable.
**Note:** The service should have `xsuaa` in its tags or the environment variable `UAA_SERVICE_NAME` should be specified (stating the exact name of the UAA service).
During local development the UAA configuration is provided in the *default-services.json* file. When the UAA is used for authentication the user is redirected to the UAA's login page to enter their credentials. Sample content for a *default-services.json* file: ```json { "uaa": { "url" : "http://my.uaa.server/", "clientid" : "client-id", "clientsecret" : "client-secret", "xsappname" : "my-business-application" } } ``` The application router supports the `$XSAPPNAME` placeholder (upper case letters). You may use it in your [route](#routes) configurations in the scope property. The value of `$XSAPPNAME` is taken from the UAA configuration (the `xsappname` property). ### Additional headers configuration If configured, the application router can send additional http headers in its responses to the client. Additional headers can be set in the `httpHeaders` environment variable. Sample configuration for additional headers: ```json [ { "X-Frame-Options": "ALLOW-FROM http://localhost" }, { "Test-Additional-Header": "1" } ] ``` In this case, the application router sends two additional headers in the responses to the client.
Custom response headers, configured in the application router configuration file (xs-app.json) are added to the list of additional http headers. If the response header name already exists in the additional http headers list, the value of the response header name overrides the value of the http header. **Caution:** For security reasons, the following headers must not be configured: authorization', 'cookie', and 'set-cookie'. ### Additional cookies configuration If configured, the application router will send additional cookie values in its responses to the client. Additional cookie values can be set in the `COOKIES` environment variable. Example of configuration for cookies in the manifest.yml : ```json env: COOKIES: > { "SameSite":"None" } ``` In this example, the application router sets the SameSite cookie attribute to None for the JSESSIONID cookie in the responses to the client. Note: Currently, only the SameSite cookie value is supported. SameSite = "Strict" is not supported. ### Plugins configuration A plugin serves almost the same purpose as [*routes*](#routes). The difference is that plugins can be configured through the environment and that way you can add new routes to the application router without changing the design-time artefact *xs-app.json*. The plugin configuration properties are the same as those of a [*route*](#routes) except that you can't configure `localDir`, `replace` and `cacheControl`. Property | Type | Optional | Description -------- | ---- |:--------:| ----------- name | String | | The name of this plugin source | String/Object | | Describes a regular expression that matches the incoming [request URL](https://nodejs.org/api/http.html#http_message_url).
**Note:** A request matches a particular route if its path __contains__ the given pattern. To ensure the RegExp matches the complete path, use the following form: ^$`.
**Note:** Be aware that the RegExp is applied to on the full URL including query parameters. target | String | x | Defines how the incoming request path will be rewritten for the corresponding destination. destination | String | | An alphanumeric name of the destination to which the incoming request should be forwarded. authenticationType | String | x | The value can be ias, xsuaa, basic, or none. The default authenticationType depends on the authentication service binding: If the application router is bound to the Identity Authentication service, the default authenticationType is ias. Otherwise, the default value is xsuaa. If xsuaa or ias are used, the specified authentication server (Identity Authentication or User Account and Authentication) handles the authentication (the user is redirected to the login form of Identity Authentication or User Account and Authentication). The basic authenticationType works with SAP HANA users, SAP ID service, and Identity Authentication service. For more information, see the SAP Note 3015211 - BASIC authentication options for SAP BTP Cloud Foundry applications. If the value none is used, no authentication is required for this route.
. csrfProtection | Boolean | x | Enable [CSRF protection](#csrf-protection) for this route. The default value is `true`. scope | Array/String/Object | x | Scopes are related to the permissions a user needs to access a resource. This property holds the required scopes to access the target path. Access is granted if the user has at least one of the listed scopes. **Note:** Scopes are defined as part of the xsuaa service instance configuration. You can use `ias` as authenticationType and xsuaa scopes for authorization if the application router is bound to both (`ias` and `xsuaa`)." Sample content of the `plugins` environment variable: ```json [ { "name": "insecurePlugin", "source": "/plugin", "destination": "plugin", "target": "/", "csrfProtection": false, "scope": ["viewer", "reader"] }, { "name": "publicPlugin", "source": "/public-plugin", "destination": "publicPlugin", "authenticationType": "none" } ] ``` ### Session timeout configuration For example, if you have the following line in your *manifest.yml* or *manifest-op.yml* file: ```yml - name: node-hello-world memory: 100M path: web env: SESSION_TIMEOUT: 40 ``` After 40 minutes of user inactivity (no requests have been sent to the application router), a Central Logout will be triggered due to session timeout. **Note:** The application router depends on the UAA server for user authentication, if the `authenticationType` for a route is `xsuaa`. The UAA server may have a different session timeout configured. It is recommended that the configurations of the application router and the UAA are identical.
### X-Frame-Options configuration Application router sends `X-Frame-Options` header by default with value `SAMEORIGIN`. This behaviour can be changed in 2 ways: - Disable sending the default header value by setting `SEND_XFRAMEOPTIONS` environment variable to `false` - Override the value to be sent via [additional headers configuration](#additional-headers-configuration) ## Cross-Origin Resource Sharing configuration The CORS keyword enables you to provide support for cross-origin requests, for example, by allowing the modification of the request header. 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. For example, you can specify where requests can originate from or what is allowed in the request and response headers. The CORS configuration can be provided in the CORS environment variable or in the CORS property of the application router configuration file (xs-app.json). If a cross-origin resource sharing (CORS) configuration exists in both the environment variables and the application router configuration file (xs-app.json), the application router gives priority to the CORS configuration in the application router configuration file. The CORS configuration is an array of objects. Here are the properties that a CORS object can have: Property | Type | Optional | Description -------- | ---- |:--------:| ----------- uriPattern| String | | A regular expression representing for which source routes CORS configuration is applicable. To ensure the RegExp matches the complete path, surround it with ^ and $. **Defaults:** none. allowedOrigin| Array | | A comma-separated list of objects that each one of them containing host name, port and protocol that are allowed by the server.for example: [{?host?: "www.sap.com"}] or [{?host?: ?*.sap.com?}]. **Note:** matching is case-sensitive. In addition, if port or protocol are not specified the default is ?_*_?. **Defaults:** none. allowedMethods| Array of upper-case HTTP methods| x | Comma-separated list of HTTP methods that are allowed by the server. **Defaults:** [?GET?, ?POST?, ?HEAD?, ?OPTIONS?] (all) applies. **Note:** matching is case-sensitive. maxAge| Number| x | A single value specifying how long, in seconds, a preflight response should be cached. A negative value will prevent CORS Filter from adding this response header to pre-flight response. **Defaults:** 1800. allowedHeaders| Array of headers| x | Comma-separated list of request headers that are allowed by the serve. **Defaults:** [?Origin?, ?Accept?, ?X-Requested-With?, ?Content-Type?, ?Access-Control-Request-Method?, ?Access-Control-Request-Headers?]. exposeHeaders| Array of headers| x | Comma-separated list of response headers (other than simple headers) that can be exposed. **Defaults:** none. allowedCredentials| Boolean| x | A flag that indicates whether the resource supports user credentials. **Defaults:** true. Sample content of the CORS environment variable: ```json [ { "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 } ] ``` It is also possible to include the CORS in the *manifest.yml* and *manifest-op.yml* files: ```yml - name: node-hello-world memory: 100M path: web env: CORS: > [ { "allowedOrigin":[ { "host":"my_host", "protocol":"https" } ], "uriPattern":"^/route1$" } ] ``` For route with source that match the REGEX ?^\route1$?, the CORS configuration is enabled. ## Direct Routing URI Patterns configuration With the direct routing URI patterns configuration, you can define a list of URIs that are directed to the routing configuration file (xs-app.json file) of the application router instead of to a specific application's xs-app.json file that is stored in the HTML5 Application Repository. This configuration improves the application loading time and monitoring options because it prevents unnecessary calls to the HTML5 Application Repository. The configuration is an array of strings or regular expressions. Note that the following regular expressions are preconfigured in the configuration array: "^favicon.ico$", "^login$". Therefore, do not name your HTML5 applications "favicon.ico" or "login"! You have to provide only the first segment in the URL, after the approuter host. For example, for the URL https://approuter-host/route1/index.html, you enter "route1" in the direct routing URI patterns array. Sample content of the Direct Routing URI Patterns environment variable: ```json env: DIRECT_ROUTING_URI_PATTERNS: > ["route1", "^route2$", "route3"] ``` ## NodeJS Minimal Logging Level configuration With this configuration, you can set the minimal logging level of the *cf-nodejs-logging-support* library of the application router. The following levels are available: * *off* * *error* * *warn* * *info* * *verbose* * *debug* * *silly* The default value is "error". Here is a sample content for the NodeJS minimal logging level environment variable: ```json env: CF_NODEJS_LOGGING_LEVEL: "debug" ``` **Note** The application router also uses the *@sap/logging* library. To configure the log level for this library, you use the *XS_APP_LOG_LEVEL* environment variable. ## Dynamic Identity Provider configuration If `dynamicIdentityProvider` is `true`, the end user can set the identity provider (IDP) for the application’s login process by filling the request query parameter `sap_idp` with the IDP Origin Key. If `IdentityProvider` property is defined in the route, its value will be overwritten by the `sap_idp` query parameter value. The default value for `dynamicIdentityProvider` is `false`. This configuration is relevant for a standalone approuter scenario and it is set for all routes. Here is a sample content for the dynamic identity provider environment variable: ```json env: DYNAMIC_IDENTITY_PROVIDER: true ``` ## Routes A route is a configuration that instructs the application router how to process an incoming request with a specific path. Property | Type | Optional | Description -------- | ---- |:--------:| ----------- source | String/Object | | Describes a regular expression that matches the incoming [request URL](https://nodejs.org/api/http.html#http_message_url).
**Note:** A request matches a particular route if its path __contains__ the given pattern. To ensure the RegExp matches the complete path, use the following form: ^$`.
**Note:** Be aware that the RegExp is applied to on the full URL including query parameters. httpMethods | Array of upper-case HTTP methods | x | Which HTTP methods will be served by this route; the methods supported are: `DELETE`, `GET`, `HEAD`, `OPTIONS`, `POST`, `PUT`, `TRACE`, `PATCH` (no extension methods are supported). If this option is not specified, the route will serve any HTTP method. target | String | x | Defines how the incoming request path will be rewritten for the corresponding destination or static resource. destination | String | x | The name of the destination to which the incoming request should be forwarded. The destination name can be a static string or a regular expression that defines how to dynamically fetch the destination name from the source property or from the host. service | String | x | The name of the service to which the incoming request should be forwarded. endpoint | String | x | The name of the endpoint within the service to which the incoming request should be forwarded. Can only be used in a route containing a service attribute. localDir | String | x | Folder in the [working directory](#working-directory) from which the application router will serve static content **Note:** localDir routes support only HEAD and GET requests; requests with any other method receive a 405 Method Not Allowed. preferLocal | Boolean | x | Defines from which subaccount the destination is retrieved. If preferLocal is true, the destination is retrieved from the provider subaccount. If preferLocal is false or undefined, the destination is retrieved from the subscriber subaccount. replace | Object | x | An object that contains the configuration for replacing placeholders with values from the environment. *It is only relevant for static resources*. Its structure is described in [Replacements](#replacements). authenticationType | String | x | The value can be `xsuaa`,`ias`, `basic` or `none`. The default one is `ias`, if subaccount trusts an ias tenant, else `xsuaa`. When `xsuaa` or `ias` are used the specified authentication server will handle the authentication (the user is redirected to the authentication service login form). The `basic` mechanism works with SAP HANA users, SAP ID Service and SAP Identity Authentication service. Find more details in SAP Note 3015211 - BASIC authentication options for SAP BTP Cloud Foundry applications. If `none` is used then no authentication is needed for this route. csrfProtection | Boolean | x | Enable [CSRF protection](#csrf-protection) for this route. The default value is `true`. scope | Array/String/Object | x | Scopes are related to the permissions a user needs to access a resource. This property holds the required scopes to access the target path. cacheControl | String | x | String representing the value of the `Cache-Control` header, which is set on the response when serving static resources. By default the `Cache-Control` header is not set. *It is only relevant for static resources.* identityProvider | String | x | The name of the identity provider to use if provided in route’s definition. If not provided, the route will be authenticated with the default identity provider. **Note:** If the authenticationType is set to Basic Authentication or None, do not define the identityProvider property. dynamicIdentityProvider | Boolean | x | If `dynamicIdentityProvider` is `true`, the end user can set the identity provider (IDP) for the application’s login process by filling the request query parameter `sap_idp` with the IDP Origin Key. If `IdentityProvider` property is defined in the route, its value will be overwritten by the `sap_idp` query parameter value. The default value for `dynamicIdentityProvider` is `false`. **Note:** The properties `destination`, `localDir` and `service` are optional, but exactly one of them must be defined.
**Note:** When using the property `replace` it is mandatory to define the `localDir` property.
**Note:** The cacheControl property is effective only when one of the following settings is performed: * The localDir property was set * A service pointing to HTML5 Application Repository ("service": "html5-apps-repo-rt") was set ### Example routes For example, if you have a configuration with the following destination: ```json [ { "name" : "app-1", "url" : "http://localhost:3001" } ] ``` Here are some sample route configurations: * Route with a `destination` and no `target` ```json { "source": "^/app1/(.*)$", "destination": "app-1" } ``` Since there is no `target` property for that route, no path rewriting will take place. If we receive */app1/a/b* as a path, then a request to *http://localhost:3001/app1/a/b* is sent. The source path is appended to the destination URL. * Route with case-insensitive matching ```json { "source": { "path": "^/app1/(.*)$", "matchCase": false }, "destination": "app-1" } ``` This example is much like the previous one, but instead of accepting only paths starting with */app1/*, we accept any variation of _app1_'s case.
That means if we receive */ApP1/a/B*, then a request to *http://localhost:3001/ApP1/a/B* is sent.
**Note:** The property `matchCase` has to be of type boolean. It is optional and has a default value `true`. * Route with a `destination` and a `target` ```json { "source": "^/app1/(.*)$", "target": "/before/$1/after", "destination": "app-1" } ``` * Route with a `service`, a `target` and an `endpoint` ```json { "source": "^/odata/v2/(.*)$", "target": "$1", "service": "com.sap.appbasic.country", "endpoint": "countryservice" } ``` When a request with path */app1/a/b* is received, the path rewriting is done according to the rules in the `target` property. The request will be forwarded to http://localhost:3001/before/a/b/after. **Note:** In regular expressions there is the term _capturing group_. If a part of a regular expression is surrounded with parenthesis, then what has been matched can be accessed using _$_ + the number of the group (starting from 1). In the last example _$1_ is mapped to the _(.*)_ part of the regular expression in the `source` property. * Route with dynamic `destination` and `target` ```json { "source": "^/destination/([^/]+)/(.*)$", "target": "$2", "destination": "$1", "authenticationType": "xsuaa" } ``` If you have a another destination configured: ```json [ { "name" : "myDestination", "url" : "http://localhost:3002" } ] ``` when a request with the path /destination/myDestination/myTarget is received, the destination will be replaced with the url from "myDestination", the target will get "myTarget" and the request will be redirected to http://localhost:3002/myTarget **Note:** You can use a dynamic value (regex) or a static string for both destination and target values **Note:** The approuter first looks for the destination name in the manifest.yaml file, and if not found, looks for it in the destination service. * Destination In Host For legacy applications that do not support relative URL paths, you need to define your URL in the following way to enable the destination to be extracted from the host the url should be defined in the following way: ```https://-./``` To enable the application router to determine the destination of the URL host, a DESTINATION_HOST_PATTERN attribute must be provided as an environment variable. Example: When a request with the path https://myDestination.some-approuter.someDomain.com/app1/myTarget is received, the following route is used: ```json { "source": "^/app1/([^/]+)/", "target": "$1", "destination": "*", "authenticationType": "xsuaa" } ``` In this example, the target will be extracted from the source and the ‘$1’ value is replaced with ‘myTarget’. The destination value is extracted from the host and the ‘*’ value is replaced with ‘myDestination’. * Route with a `localDir` and no `target` ```json { "source": "^/web-pages/(.*)$", "localDir": "my-static-resources" } ``` Since there is no `target` property for that route, no path rewriting will take place. If we receive a request with a path */web-pages/welcome-page.html*, the local file at *my-static-resources/web-pages/welcome-page.html* under the working directory will be served. * Route with a `localDir` and a `target` ```json { "source": "^/web-pages/(.*)$", "target": "$1", "localDir": "my-static-resources" } ``` If we receive a request with a path '/web-pages/welcome-page.html', the local file at 'my-static-resources/welcome-page.html' under the working directory will be served.
**Note:** The capturing group used in the `target` property. * Route with `localDir` and `cacheControl` ```json { "source": "^/web-pages/", "localDir": "my-static-resources", "cacheControl": "public, max-age=1000,must-revalidate" } ``` * Route with `service` "html5-apps-repo-rt" and `cacheControl` ```json { "source": "^/index.html$", "service": "html5-apps-repo-rt", "authenticationType": "xsuaa", "cacheControl":"public,max-age=1000,must-revalidate" } ``` * Route with `httpMethods` restrictions The `httpMethods` option allows you to split the same path across different targets depending on the HTTP method. For example: ```json { "source": "^/app1/(.*)$", "target": "/before/$1/after", "httpMethods": ["GET", "POST"] } ``` This route will be able to serve only GET and POST requests. Any other method (including extension ones) will get a 405 Method Not Allowed response. The same endpoint can be split across multiple *destinations* depending on the HTTP method of the requests: ```json { "source": "^/app1/(.*)$", "destination" : "dest-1", "httpMethods": ["GET"] }, { "source": "^/app1/(.*)$", "destination" : "dest-2", "httpMethods": ["DELETE", "POST", "PUT"] } ``` The setup above will route GET requests to the target *dest-1*, DELETE, POST and PUT to *dest-2*, and any other method receives a 405. It is also possible to specify "catchAll" routes, namely those that do not specify `httpMethods` restrictions: ```json { "source": "^/app1/(.*)$", "destination" : "dest-1", "httpMethods": ["GET"] }, { "source": "^/app1/(.*)$", "destination" : "dest-2" } ``` In the setup above, GET requests will be routed to *dest-1*, and all the rest to *dest-2*. Why using `httpMethods`? It is often useful to split the implementation of microservices across multiple, highly specialized applications. For example, a Java application written to serve high amounts of GET requests that return large payloads is implemented, sized, scaled and load-tested differently than applications that offer APIs to upload limited amounts of data. `httpMethods` allows you to split your REST APIs, e.g., */Things* to different applications depending on the HTTP methods of the requests, without having to make the difference visible in the URL of the endpoints. Another usecase for `httpMethods` is to "disable" parts of the REST API. For example, it may be necessary to disable some endpoints that accept DELETE for external usage. By allowing only certain methods in the route, you can hide functionalities of your microservice that should not be consumable without having to modify the code or configurations of your service. **Note:** `localDir` and `httpMethods` are incompatible. The following route is invalid: ```json { "source": "^/app1/(.*)$", "target": "/before/$1/after", "localDir": "resources", "httpMethods": ["GET", "POST"] } ``` However, since `localDir` supports only GET and HEAD requests, returning 405 to requests with any other method, any `localDir` route is "implicitly" restricted in terms of supported HTTP methods. * Route with a `scope` An application specific scope has the following format: ``` . ``` It is possible to configure what scope the user needs to possess in order to access a specific resource. Those configurations are per [route](#routes). In this example, the user should have **at least** one of the scopes in order to access the corresponding resource. ```json { "source": "^/web-pages/(.*)$", "target": "$1", "scope": ["$XSAPPNAME.viewer", "$XSAPPNAME.reader", "$XSAPPNAME.writer"] } ``` For convenience if our route requires only one scope the `scope` property can be a string instead of an array. The following configuration is valid as well: ```json { "source": "^/web-pages/(.*)$", "target": "$1", "scope": "$XSAPPNAME.viewer" } ``` You can configure scopes for the different HTTP methods (GET, POST, PUT, HEAD, DELETE, CONNECT, TRACE, PATCH and OPTIONS). If some of the HTTP methods are not explicitly set, the behaviour for them is defined by the `default` property. In case there is no `default` property specified and the HTTP method is also not specified, the request is rejected by default. ```json { "source": "^/web-pages/(.*)$", "target": "$1", "scope": { "GET": "$XSAPPNAME.viewer", "POST": ["$XSAPPNAME.reader", "$XSAPPNAME.writer"], "default": "$XSAPPNAME.guest" } } ``` The application router supports the `$XSAPPNAME` placeholder. Its value is taken (and then substituted in the routes) from the UAA configuration. You may read more about it [here](#uaa-configuration).
**Note:** The substitution is case sensitive. You can use the name of the business application directly instead of using the `$XSAPPNAME` placeholder: ```json { "source": "^/backend/(.*)$", "scope": "my-business-application.viewer" } ``` * Route with an `identityProvider` For example, we can define several identity providers for different types of users. In this example, there are 2 categories: hospital patients and hospital personnel: 1. patientsIDP – use for authenticating patients. 2. hospitalIDP – use for authenticating all hospital personnel (doctors, nurses etc..). We can configure 2 routes with the following identityProvider properties: ```json [ { "source": "^/patients/sap/opu/odata/(.*)", "target": "/sap/opu/odata$1", "destination": "backend", "authenticationType": "xsuaa", "identityProvider": "patientsIDP" }, { "source": "^/hospital/sap/opu/odata/(.*)", "target": "/sap/opu/odata$1", "destination": "backend", "authenticationType": "xsuaa", "identityProvider": "hospitalIDP" } ] ``` So, a patient who tries to log into the system will be authenticated by patientIDP, and a doctor who tries to log in will be authenticated by hospitalIDP. **Note:** After logging in using one of the identity providers, to switch to the other one it is necessary to logout and perform a new log in. **Note:** Currently, dynamic provisioning of the subscriber account identity provider is not supported. **Note:** Identity provider configuration is only supported in the client side login redirect flow. * Route with a `dynamicIdentityProvider` For example, we can define a route where the value of `identityProvider` is `patientsIDP` and where dynamic identity provider provisioning is enabled by setting `dynamicIdentityProvider` to `true`: ```json [ { "source": "^/patients/index.html", "target": "/patients-index.html", "service": "html5-apps-repo-rt", "identityProvider": "patientsIDP", "dynamicIdentityProvider": true } ] ``` In this example, the `patientsIDP` value for the `identityProvider` is replaced by `hospitalIDP` if a request with `sap_idp=hospitalIDP` is executed, for example, if the request is https://shiva.health-center-approuter.cfapps.hana.ondemand.com/healthreport/patients/index.html?sap_idp=hospitalIDP. ## Replacements This object configures the placeholder replacement in static text resources. Property | Type | Description -------- | ---- | ----------- pathSuffixes | Array | An array containing the path suffixes that are relative to `localDir`. Only files with a path ending with any of these suffixes will be processed. vars | Array | A list with the environment variables that will be replaced in the files matching the suffix. services | Object | An object describing bound services that will provide replacement values. Each property of this object is used to lookup a separate service. The property names are arbitrary. Service lookup format is described in _Service Query_ section in _@sap/xsenv_ documentation. The supported tags for replacing environment variables are: `{{ENV_VAR}}` and `{{{ENV_VAR}}}`. If there is such an environment variable it will be replaced, otherwise it will be just an empty string. For services you can specify a property from the `credentials` section of the service binding which will be replaced. For example `{{{my_service.property}}}` and `{{my_service.property}}` Every variable that is replaced using two-brackets syntax will be HTML-escaped. For example if the value of the environment variable is `ab"cd` the result will be `ab"cd`.
The triple brackets syntax is used when the replaced values don't need to be escaped and all values will be unchanged. For example, if somewhere in your *xs-app.json* you have a route: ```json { "source": "^/get/home(.*)", "target": "$1", "localDir": "resources", "replace": { "pathSuffixes": ["index.html"], "vars": ["escaped_text", "NOT_ESCAPED"], "services": { "my-sapui5-service": { "tag": "ui5" } } } } ``` and you have the following `index.html`: ```html {{escaped_text}}