Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
dongpan
Advisor
Advisor
Cross-Origin Resource Sharing (CORS) is a W3C specification that allows cross-domain communication from the browser. By building on top of the AJAX/XMLHttpRequest object, CORS allows developers to work in the same coding paradigm as with same-domain requests. CORS has started to play a more and more important role in today's web and cloud based applications, while our web applications are trending towards system/data integration across domains. Web application servers that support CORS make it possible for a clean architecture, without using reverse proxies or other forms of middle tier.

A majority of SAP applications reside on top of the SAP NetWeaver Application Server platform, from which many web applications retrieve data. If the data retrieval needs to happen in the web browser with AJAX calls, the traditional method to bypass web browser's Same Origin Policy is to setup a reverse proxy in front of both the web server and the SAP NetWeaver Application Server, so that they appear to the web browser as if they shared the same host name. While this may be a handy workaround, it does not only have a higher TCO for maintaining the solution, but also causes implications on SSL, authentication and Single Sign-On options.

But as a matter of fact, it is technically possible to configure SAP NetWeaver Application Server to support CORS, so that your web application landscape can be greatly simplified as below:



 

The trick is simple. Add a rewrite rule for NetWeaver's ICM component, so that it returns the necessary CORS headers.

First of all, make sure your NetWeaver system's kernel patch level meets the prerequisites:

  • Kernel 7.49 PL 824

  • Kernel 7.53 PL 610

  • Kernel 7.73 PL 242

  • Kernel 7.77 PL 112

  • Kernel 7.79 PL 16


Next, configure the NetWeaver Application Server's Default profile, enable HTTP rewriting and point to the action/rewrite file. In the below example, the action file is the rewrite.txt file in the system profiles' folder. In addition, set profile parameter icm/HTTP/samesite to None so that the SameSite cookie attribute is set properly. Do not set the profile parameter icm/HTTP/samesite_none_add_secure, icm/HTTP/samesite_user_agents and icm/HTTP/samesite_exclude_user_agents.
icm/HTTP/mod_0 = PREFIX=/,FILE=$(DIR_PROFILE)/rewrite.txt
icm/HTTP/samesite = None

In the action file, maintain the following settings to inject the necessary CORS headers. Make sure you specify your web server's URL as the value of the Access-Control-Allow-Origin header.
#Author: Dong Pan, dong.pan@sap.com
if %{HEADER:ORIGIN} stricmp https://webserver1 [OR]
if %{HEADER:ORIGIN} stricmp https://webserver2 [OR]
if %{HEADER:ORIGIN} stricmp https://webserver3
begin
SetResponseHeader Access-Control-Allow-Origin %{HEADER:ORIGIN}
SetResponseHeader Access-Control-Allow-Credentials true
SetResponseHeader Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS"
SetResponseHeader Access-Control-Allow-Headers "X-Csrf-Token, x-csrf-token, x-sap-cid, Content-Type, Authorization, mysapsso2"
SetResponseHeader Access-Control-Expose-Headers "x-csrf-token"
SetResponseHeader Access-Control-Max-Age 600
end

 

If the NetWeaver kernel patch level is lower than the above-mentioned prerequisites, but higher than 7.49 PL 315, an additional code snippet is required to set the SameSite cookie attribute properly for the third-party cookies. Below is a sample script.
#Author: Dong Pan, dong.pan@sap.com
if %{HEADER:ORIGIN} stricmp https://webserver1 [OR]
if %{HEADER:ORIGIN} stricmp https://webserver2 [OR]
if %{HEADER:ORIGIN} stricmp https://webserver3
begin
SetResponseHeader Access-Control-Allow-Origin %{HEADER:ORIGIN}
SetResponseHeader Access-Control-Allow-Credentials true
SetResponseHeader Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS"
SetResponseHeader Access-Control-Allow-Headers "X-Csrf-Token, x-csrf-token, x-sap-cid, Content-Type, Authorization, mysapsso2"
SetResponseHeader Access-Control-Expose-Headers "x-csrf-token"
SetResponseHeader Access-Control-Max-Age 600
end

#Support the SameSite cookie attribute for Third-Party Cookies
SetHeader sap-ua-protocol ""
if %{HEADER:clientprotocol} stricmp http [OR]
if %{HEADER:x-forwarded-proto} stricmp http [OR]
if %{HEADER:forwarded} regimatch proto=http
begin
SetHeader sap-ua-protocol "http"
end
if %{HEADER:clientprotocol} stricmp https [OR]
if %{HEADER:x-forwarded-proto} stricmp https [OR]
if %{HEADER:forwarded} regimatch proto=https
begin
SetHeader sap-ua-protocol "https"
end
if %{HEADER:sap-ua-protocol} strcmp "" [AND]
if %{SERVER_PROTOCOL} stricmp https
begin
SetHeader sap-ua-protocol "https"
end
if %{RESPONSE_HEADER:set-cookie} !strcmp "" [AND]
if %{HEADER:sap-ua-protocol} stricmp https [AND]
if %{HEADER:user-agent} regmatch "^Mozilla" [AND]
if %{HEADER:user-agent} !regmatch "(Chrome|Chromium)/[1-6]?[0-9]\." [AND]
if %{HEADER:user-agent} !regmatch "(UCBrowser)/([0-9]|10|11|12)\." [AND]
if %{HEADER:user-agent} !regmatch "\(iP.+; CPU .*OS 12_.*\) AppleWebKit\/" [AND]
if %{HEADER:user-agent} !regmatch "\(Macintosh;.*Mac OS X 10_14.*(Version\/.* Safari.*|AppleWebKit\/[0-9\.]+.*\(KHTML, like Gecko\))$"
begin
RegIRewriteResponseHeader set-cookie "^([^=]+)(=.*)" "$1$2; SameSite=None; Secure"
RegIRewriteResponseHeader set-cookie "^([^=]+)(=.*; *SameSite=[a-zA-Z]+.*); SameSite=None; Secure" $1$2
RegIRewriteResponseHeader set-cookie "^([^=]+)(=.*; *Secure.*); Secure" $1$2
end

Note: in the below sections of the blog post, it is assumed that NetWeaver kernel patch level meets the prerequisites, so the additional samesite-related code section is not included.

Restart the NetWeaver Application Server, and you are all set. The above settings will turn on CORS support and allow CORS requests originated from the three web servers.

 

Turn on CORS per application


The above settings will turn on CORS support for the entire NetWeaver application server. If you want to enable CORS for a specific application/path only, you can create the rewrite rules like below:
#Author: Dong Pan, dong.pan@sap.com
if %{HEADER:ORIGIN} stricmp https://webserver1 [AND]
if %{PATH} regimatch /app1/path1/*
begin
SetResponseHeader Access-Control-Allow-Origin %{HEADER:ORIGIN}
SetResponseHeader Access-Control-Allow-Credentials true
SetResponseHeader Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS"
SetResponseHeader Access-Control-Allow-Headers "X-Csrf-Token, x-csrf-token, x-sap-cid, Content-Type, Authorization, mysapsso2"
SetResponseHeader Access-Control-Expose-Headers "x-csrf-token"
end

With the above setting, only the application under the /app1/path1/* path is CORS-enabled. You can even fine-tune the allowed HTTP request methods to reflect what request methods the application supports. Pretty cool, right?

 

Handling Stateful Applications


Many SAP web applications implement "URL rewriting" to achieve stateful session management. In this case, the web browser/JavaScript is supposed to add a long session ID dynamically to the URL, for example, the request to /sap/bw/ina would change to something like below after authentication:

/sap(cz1TSUQlM2FBTk9OJTNhQlc3NTBfQjc1XzAwJTNhMFR2WnAxdzRoanVuU2gzODRHbk9ydUF5aTRDYzd4WWZrdFEzMlBjRi1BVFQ=)/bw/ina/GetResponse

The part in red is the dynamic session ID that keeps changing from request to request in the entire dialog between the Web Browser and NetWeaver application server. This is done via two HTTP response headers:

  1. sap-rewriteurl

  2. sap-url-session-id


The JavaScript in the web browser needs to be able to pick up the values of the above HTTP  response headers, in order to construct the dynamic URLs containing the session ID according to the NetWeaver applications server's instruction. Since these headers come from a CORS response, we need to add them to the Access-Control-Expose-Headers list.

We also need to fine-tune the condition expression so that it continues to match the application's path, even when the session ID is injected to it. We will leverage the powerful regular expression support here. Below is a sample rules file:
#Author: Dong Pan, dong.pan@sap.com
if %{HEADER:ORIGIN} stricmp https://webserver1 [OR]
if %{HEADER:ORIGIN} stricmp https://webserver2 [AND]
if %{PATH} regimatch /sap(\(.+\))*/app1/path1/*
begin
SetResponseHeader Access-Control-Allow-Origin %{HEADER:ORIGIN}
SetResponseHeader Access-Control-Allow-Credentials true
SetResponseHeader Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS"
SetResponseHeader Access-Control-Allow-Headers "X-Csrf-Token, x-csrf-token, x-sap-cid, Content-Type, Authorization, mysapsso2"
SetResponseHeader Access-Control-Expose-Headers "x-csrf-token, sap-rewriteurl, sap-url-session-id"
end

 

Take Care of Preflight CORS Requests


The above settings will work well with Simple CORS requests only. A Simple CORS request is a CORS request that makes use of HTTP request methods GET, POST, HEAD only, and carries a limited set of HTTP headers. If a CORS request uses any other HTTP request method, such as DELETE or PUT, or if it carries HTTP headers outside of the limited set headers mentioned above, the request must be preceded with a Preflight Request, which is in essence an HTTP OPTIONS request with certain CORS-specific headers. Unfortunately the Preflight CORS request will fail with the above settings. If you would like to get a deeper understanding of Simple CORS requests and Preflight Requests, see more details here.

Update on May 26, 2022

In addition to the triggering criteria of preflight requests as explained in above-linked article, according to the new Private Network Access specification, when a CORS request is initiated by a public web site towards an internal (private) web site, a preflight request will also be triggered.

 

But why? We have already configured ICM to issue all the CORS response headers, why is the Preflight Request failing? The issue lies in how a Preflight Request is constructed. According to the CORS specification, the Preflight Request must NOT carry any user credential. As most applications on NetWeaver require user authentication, the Preflight Request will get an "HTTP 401 Unauthorized" error message, thus failing the request.

There are multiple ways to address the issue, but the cleanest solution that solves the issue without introducing any extra infrastructure components looks like below:

  1. Create a dummy node with SICF that allows anonymous access

  2. In ICM rewrite rules, redirect Preflight Requests to the dummy node that allows anonymous access

  3. Make sure that the originally-requested URL is preserved, so that CORS response headers are issued according to the application.


In this way, the Preflight Request will get the desired CORS response headers based on the requested application without being turned down due to authentication errors.

For Step 1, you can create a dummy node in SICF and name it "cors' for example. Serve the node with the handler CL_HTTP_EXT_PING, which exists on any NetWeaver ABAP system and produces minimum amount of traffic.



Make sure anonymous access is allowed for this node.



For step 2&3, we will redirect the Preflight OPTIONS request to the dummy node created above, preserve the originally-requested path in an HTTP header, and issue the CORS response headers accordingly. Here goes a sample rewirte rules file that enables CORS for the BW Info Access (InA) service:
#Author: Dong Pan, dong.pan@sap.com
SetHeader OriginalPath ""

if %{HEADER:ORIGIN} stricmp https://webserver1 [OR]
if %{HEADER:ORIGIN} stricmp https://webserver2 [AND]
if %{REQUEST_METHOD} stricmp OPTIONS
begin
RegRewriteUrl ^/.* /cors?%{QUERY_STRING} [noescape]
SetHeader OriginalPath %{PATH}?%{QUERY_STRING}
end

if %{HEADER:ORIGIN} stricmp https://webserver1 [OR]
if %{HEADER:ORIGIN} stricmp https://webserver2 [AND]
if %{HEADER:OriginalPath} regimatch /sap(\(.+\))*/bw/ina/* [AND]
if %{PATH} regmatch /cors
begin
SetResponseHeader Access-Control-Max-Age 600
SetResponseHeader Access-Control-Allow-Origin %{HEADER:ORIGIN}
SetResponseHeader Access-Control-Allow-Credentials true
SetResponseHeader Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS"
SetResponseHeader Access-Control-Allow-Headers "X-Csrf-Token, x-csrf-token, x-sap-cid, Content-Type, content-type, Authorization, mysapsso2"
SetResponseHeader vary "Origin"
RemoveResponseHeader set-cookie
RemoveResponseHeader expires
end

#Support the Private Network Access (PNA) specification
if %{HEADER:ORIGIN} stricmp https://webserver1 [OR]
if %{HEADER:ORIGIN} stricmp https://webserver2 [AND]
if %{HEADER:OriginalPath} regimatch /sap(\(.+\))*/bw/ina/* [AND]
if %{PATH} regmatch /cors [AND]
if %{HEADER:Access-Control-Request-Private-Network} stricmp true
begin
SetResponseHeader Access-Control-Allow-Private-Network true
end

if %{HEADER:ORIGIN} stricmp https://webserver1 [OR]
if %{HEADER:ORIGIN} stricmp https:///webserver2 [AND]
if %{PATH} regimatch /sap(\(.+\))*/bw/ina/*
begin
SetResponseHeader Access-Control-Allow-Origin %{HEADER:ORIGIN}
SetResponseHeader Access-Control-Allow-Credentials true
SetResponseHeader Access-Control-Expose-Headers "x-csrf-token, sap-rewriteurl, sap-url-session-id"
SetResponseHeader vary "Origin"
end

 

Restart the NetWeaver system, and it is now able to serve preflighted CORS requests as well. With some simple configuration steps, your NetWeaver system is now able to provide full support for CORS! Hooray!!! Isn't that cool?!

On a side note: if you are sending Preflighted CORS requests to a NetWeaver Java system, you can skip step 1. As there are a number of anonymous HTML pages on a vanilla NetWeaver Java system, e.g. the home page, you can make use of any of them, or create a dummy anonymous page that produces minimum traffic.

 

Turn on CORS in a NetWeaver Cluster


In a large deployment of SAP NetWeaver landscape, it is often the case where there are multiple NetWeaver server instances in a cluster. In this case, you have two options to turn on CORS:

  1. Turn on CORS as per the above steps. Since the steps above are based on the Default profile, and the path to the rewrite.txt file is in the cluster's central share (pointed to by the $(DIR_PROFILE) parameter), the CORS settings apply to all server instances automatically.

  2. Turn on CORS on the SAP Web Dispatcher that sits in front of the NetWeaver cluster as its load balancer. With the latest version of Web Dispatcher (7.49 PL824 or above), you can use exactly the same rewrite rules on Web Dispatcher too.


Generally speaking, I would recommend Option 1, as it is a consistent approach across single-instance NetWeaver clusters and multi-instance NetWeaver clusters. It also follows the principal of keeping together the CORS logic and the application server. In cases where such a setup is impossible for any reason, Option 2 can be used as an alternative.

 

Start to enjoy the beauty and simplicity of CORS!

 

[Update 2018.10] With the following NetWeaver AS ABAP versions, SAP provides built-in native support for CORS. See SAP Note 2547381 for details.

  • NetWeaver AS ABAP 7.52 SP02

  • NetWeaver AS ABAP 7.51 SP06

  • NetWeaver AS ABAP 7.50 SP12

  • NetWeaver AS ABAP 7.40 SP20


The configuration details can be found here. You can continue to use the method in this blog post to enable CORS if your NetWeaver AS ABAP system is of a version lower than the above-mentioned versions, or if you are using NetWeaver AS Java.

 

[Update 2020.02] Added support for the SameSite cookie attribute. I wrote a blog post on sapanalytics.cloud to explain the impact caused by the SameSite cookie attribute, and the additional ICM rewrite rules needed address the issue. The blog can be found here: Direct Live Connections in SAP Analytics Cloud and SameSite Cookies.

 

[Update 2022.05]Added support for the Private Network Access (PNA) specification. Simplified script as the SameSite support is now provided by NetWeaver kernel.
38 Comments