Can't find what you are looking for? Try these pages!

Blog

Cross Origin Resource Sharing (CORS) with DataFlex WebApps

By Mike Peat

What is CORS?

Well, as you might have guessed from the title of this article, it is Cross-Origin Resource Sharing. CORS is a W3C recommended standard (not yet quite a full standard, but nearly so) and has long since been implemented in most modern web browsers. It is a way of allowing web developers to work around the security measures of the same-origin policy which only allows web pages to interact with resources from the same host or domain.

If you want to make your DataFlex web applications available for use by other sites, there are a number of ways you can go about it, some of which require the use of CORS.

Do you need to use CORS?

For the simplest of implementations, you don’t have to use CORS. For example, the easiest method for putting your application on another web site is simply to have a link (or it could be a button, or something similar) on the host web site that takes the user to your application (perhaps in another browser tab or window). However, this is not really embedding.

Alternatively, to place your application actually within a page on the host site, you could use an HTML <iframe> tag, with the "src" attribute of that containing the URL of your application’s index.html (or similar) page, giving the iframe height and width attributes to suit. Effectively this is a kind of "picture-in-picture" technique: the host web page is the main picture, your app in the iframe is a second picture in a box within it. This approach will place your application alongside the host’s site’s content, while keeping them isolated from one another.

However if you want your application’s components to sit on the host site’s page as native elements within it, by having the steps usually performed by your application’s index.html page be dealt with by the host page instead, things can get a little more complicated.

If your application is running on the same server as the site your are embedding it in, then again, there is no problem. However, for various reasons, this may not always be possible. One reason that this might be the case is if the host site is running on an operating system other than Windows - Unix or Linux for instance.

The problem comes from the fact that DataFlex web applications use the web browser’s XmlHttpRequest (usually abbreviated as "XHR") object to first load the client side of your application into the browser and then for that to communicate with your running DataFlex application on your server.

The XHR object has, by design, through its same-origin policy, restrictions on where it may make its requests to. Essentially these are limited to the same server (technically "host") the page it is in was loaded from. With some fiddling, these can theoretically be relaxed to being within the same "domain" - an XHR object loaded from www.someCompany.com could be allowed to make requests to apps.someCompany.com, but no farther than that. In fact even that is not simple for DataFlex applications, because it involves setting DOM properties of a page and the "page" of a DataFlex web application is created dynamically through a call to the WebServiceDispatcher.wso object in your workspace’s AppHTML directory.

This is where CORS comes in. It allows web developers to cleanly work around the same-origin policy enforced by browsers on XHR requests in a standards-compliant fashion.

Using CORS

Perhaps surprisingly, using CORS does not involve making changes to the host web site, but rather to the site which is the source the XHR is making requests of. When such a request is made the web browser first makes what is called a "preflight" HTTP OPTIONS request of the source to see if is happy to allow it to proceed. (Technically this only actually happens with XHR requests which are not made with the GET or HEAD HTTP verbs, or with the POST verb using only standard content-type and no custom HTTP headers, but we need not consider those as the DataFlex Web Framework’s requests don’t fit those limitations.)

The source site may respond to this preflight OPTIONS request with various "Access-Control-Allow-..." HTTP response headers. The most critical of these is "Access-Control-Allow-Origin" which can be set to allow access for a specific hostname (www.example.com) or IP address, or an asterisk "*" wildcard allowing all origins. Other relevant response headers are "Access-Control-Allow-Headers", "Access-Control-Allow-Methods" and "Access-Control-Allow-Credentials". One condition, which we will see later is important, is that the "Allow-Origin" header returning a wildcard is incompatible with "Allow-Credentials".

Getting CORS to work

There are two ways to enable the required HTTP response headers.

Using Microsoft’s IIS Manager to enable custom HTTP response headers

The first is to use Microsoft’s IIS Manager. In that, go to your Web App’s virtual directory, then in "Features View" (bottom tabs) open the HTTP "Response Headers" feature:

CORS iis manager features view.png.650x433.6

There you click "Add..." (top of the right-hand pane):

CORS HTTP Response Headers Add.png.650x162.6

And add the four "Access-Control-Allow-..." headers with values for each.

CORS Add Custom HTTP Response Header.png.350x226.6

For our purposes, the following values will be sufficient (although you could add more methods and headers):

  • Header: Access-Control-Allow-Origin
    • Value: The hostname or IP address you wish to embed your application in, of the form http://www.example.com or https://www.example.com or http://123.123.123.123
  • Header: Access-Control-Allow-Methods
    • Value: GET, POST
  • Header: Access-Control-Allow-Headers
    • Value: content-type
  • Header: Access-Control-Allow-Credentials
    • Value: true

Using an XML web.config file to enable custom HTTP response headers

The second approach is to create a file in the virtual directory called "web.config" with the following XML (this does exactly the same thing as the first approach: IIS Manager simply writes to that file):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <httpProtocol>
            <customHeaders>
               <add name="Access-Control-Allow-Origin" value="http://remoteServer" />
               <add name="Access-Control-Allow-Headers" value="content-type" />
               <add name="Access-Control-Allow-Methods" value="GET, POST" />
                <add name="Access-Control-Allow-Credentials" value="true" />
            </customHeaders>
        </httpProtocol>
   </system.webServer>
</configuration>

If a web.config file already exists, to guard against accidental errors breaking the virtual directory, make sure you take a backup copy first. Then you should add those custom headers to it, taking care to place them correctly within the existing XML structure.

Note that we are setting "Access-Control-Allow-Credentials" to true, which as we have discussed above, is incompatible with setting "Access-Control-Allow-Origin" to the wildcard "*". The "allow credentials" setting controls whether or not HTTP authentication, client-side SSL certificates and, crucially for us using the DataFlex Web Framework, cookies can be passed with the XHR requests.

The custom headers now being set up, we also have to ensure that the OPTIONS handler grabs the preflight request before anything else does. We can do this in Microsoft’s IIS manager by, in the "Features View", selecting the "Handler Mappings" feature:

CORS Features view Handler mappings.png.650x434.6

In that, click on the "View Ordered List…" link in the right-hand pane:

CORS Handler mappings move up 1.png.650x112.6

In that, find the OPTIONSVerbHandler (it will probably be close to the bottom of the list):

CORS OPTIONSVerbhandler1.png.650x63.6

In the right-hand page, click: Move up:

CORS Handler mappings move up 1.png.650x112.6

A dialog will come up asking if you really want to change the list order: click "Yes", then continue to click "Move Up" until the OPTIONSVerbHandler is at the top of the list.

One final step remains and this is to modify the behaviour of the XHR object used by the Web App to make its requests "withCredentials" (in order that the DataFlex session cookie can be sent with each request) - this is the counterpart of setting the "Access-Control-Allow-Credentials" response header to true.

In the code which invokes your web app, immediately after the JavaScript line creating the web app, add the line: "oWebApp.pbXHRWithCredentials = true;", so the pair should then look like:

var oWebApp = new df.WebApp("//yourHost/pathToApp/WebServiceDispatcher.wso");
oWebApp.pbXHRWithCredentials = true;

All this done, you should be able to embed your application in the host web site.

Enabling Embedding in Any Web Site

However, what if you want your application to be capable of being embedded in multiple web sites, or indeed in any web site at all? We have seen that using - as we require to - Access-Control-Allow-Credentials=true is at odds with using a wildcard in Access-Control-Allow-Origin. If credentials are allowed, then only a single origin is permitted. The browser will enforce this: if Allow-Credentials is true then Allow-Origin must exactly match the host of the page making the request. How can we overcome that?

Well there is a way, but it involves a bit of sleight-of-hand in getting IIS to dynamically generate that Access-Control-Allow-Origin header.

First, remove the Access-Control-Allow-Origin custom header again from your virtual directory (open the HTTP Response Headers feature, select that header and click "Remove" in the right-hand pane), since we are going to generate it dynamically and having an additional static header might (and probably would) cause trouble.

Next, there are the same two approaches to dynamically generating the header as there were to creating the custom headers: we can do it through IIS Manager (using the URL rewrite feature) or we can do it directly in XML, however since the IIS Manager route is so complex it would take a long time to detail, we are only going to deal here with the XML approach. (Note: the URL Rewrite module must be installed as a Windows Feature, however this is automatically the case for Windows Server 2012.)

In the root of the web site (NOTE: not the virtual directory, nor the Server root) from which you are serving your DataFlex Web App (which will often be "Default Web Site" and by default will be in C:\inetpub\wwwroot, but may be different if you have created others), place the following XML in the file web.config:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
        <rules>
            <rule name="CaptureRequestOrigin">
                <match url=".*" />
                <conditions>
                    <add input="{HTTP_ORIGIN}" pattern=".+" />
                </conditions>
                <serverVariables>
                    <set name="CAPTURED_ORIGIN" value="{C:0}" />
                </serverVariables>
               <action type="None" />
           </rule>
      </rules>
      <outboundRules>
            <rule name="SetAccessControlAllowOrigin">
                   <match
                       serverVariable="RESPONSE_Access-Control-Allow-Origin"
                       pattern=".+" negate="true" />
                   <conditions>
                       <add input="{CAPTURED_ORIGIN}" pattern=".+" />
                   </conditions>
                   <action type="Rewrite" value="{C:0}" />
               </rule>
           </outboundRules>
       </rewrite>
    </system.webServer>
</configuration>

If a web.config file already exists in that location, then again, you should fit the above two rules (CaptureRequestOrigin and SetAccessControlAllowOrigin) in the appropriate place within it (and again, even more importantly this time, take a backup first).

To unpack what is going on here a little, the first - inbound - rule (CaptureRequestOrigin) says: "take the HTTP ORIGIN header and set the server variable CAPTURED_ORIGIN to its value", while the second - outbound - rule (SetAccessControlAllowOrigin) says: "set the response header Access-Control-Allow-Origin to the value in the server variable CAPTURED_ORIGIN", but I am sure you had already worked that out for yourself!

Then, using IIS Manager, again in the root of the web site, open the URL Rewrite Feature:

CORS Open iis manager URL rewrite feature

If all has gone well, you should see your two new rules, one Inbound and one Outbound, displayed there (you could have created them here, but as mentioned above, it is a complex process).

Click the "View Server Variables" link in the right-hand pane:

CORS URL rewrite view server variables.png

Then click "Add...":

CORS Server Variables Add.png

Enter "CAPTURED_ORIGIN" and click "OK". Click "Add…" again and enter "RESPONSE_Access-Control-Allow-Origin", then "OK", and you are done.

Your web application should now be embeddable in any web site page.

Restricting to Specific Sites

If you need to embed your application in more than one site, but do not want to make it possible for it to be embedded in just any site, then a further refinement to the above will be required.

In IIS Manager, again at the site level, open the "URL Rewrite" feature:

CORS restrict to specific sites open url rewrite

In URL Rewrite, click on the "View Rewrite Maps" link in the right-hand pane:

CORS view rewrite maps

There, click on the "Add Rewrite Map…" link in the right-hand pane:

CORS add rewrite map

In the dialog which will pop-up, enter a name for the map ("AllowedOrigins" for example) and click "OK".

Click on the "Add Mapping Entry…" link in the right-hand pane:

CORS add mapping entry.png

In the pop-up dialog, enter the hostname of a site you wish to allow your app to be embedded in both "Original value" and the "New value", then click OK. Repeat for each of the sites you wish to allow your app to be embedded in.

Finally, modify the web.config file at the site level, changing the line in the outbound rule conditions: <add input=" ... /> to read (assuming you named your map "AllowedOrigins"):

<add input="{AllowedOrigins:{CAPTURED_ORIGIN}}" pattern=".+" />

And save the file.

It should now be possible to embed your application in the sites you have specified and only in those sites.

Additional sites can be added, or existing sites removed, either with the IIS Manager "Edit Rewrite Map" capability above and using "Add Mapping Entry…" or "Remove", or simply by modifying the web.config file "rewriteMaps" section, which will look something like:

<rewriteMaps>
    <rewriteMap name="AllowedOrigins">
        <add key="www.dataaccess.com" value="www.dataaccess.com" />
        <add key="www.front-it.dk" value="www.front-it.dk" />
        <add key="www.dataaccess.eu" value="www.dataaccess.eu" />
    </rewriteMap>
</rewriteMaps>