Develop for Cross-Origin Resource Sharing (CORS)

A short example of leveraging CORS to access AEM content from an external web application via client-side JavaScript. This example uses the CORS OSGi configuration to enable CORS access on AEM. The OSGi configuration approach is viable when:

  • A single origin is accessing AEM Publish content
  • CORS access is required for AEM Author

If multi-origin access to AEM Publish is required, refer to this documenation.

Transcript
Let’s take a look at the Cross Origin Resource Sharing functionality or, as it’s usually referred to, CORS, that was introduced in AEM 6.3. So, for this I’ve created a simple HTML page that makes an AJAX call using JQuery to my AEM publish instance. This AJAX call collects the JSON representation of a page and then injects the AEM publishers page title into my HTML pages DOM.
Two things are important to note here: The first is that I am accessing my HTML page through www.example.com:8000. This is just a simple http server that I started up on my local instance. The second is that I’m accessing my AEM publish server through a completely different domain and that’s AEM-publish.local. Due to browser restrictions, Javascript-initiated requests to other domains must be CORS-enabled. Do note the preferred approach is to host cross-resource accessing web applications on the same domain or subdomain, which negates the need for CORS entirely. But if the web applications must be hosted on discrete domains, CORS is needed. Let’s take a look at what happens with our test page when CORS is not enabled. So, for this, I’ll open up my Network tab so we can watch our network traffic and click the button which issues the AJAX request to our AEM publish instance. The first thing we see is that our page title is not AJAX’d in. We can take a look at our response and our response does have the JSON that we expect. But if we go with the Javascript Console, we’ll see we have an error stating that the AJAX request is not allowed access to the content. Let’s fix this by adding a CORS policy configuration to our AEM publish instance. So, I’ll go ahead and Copy the Origin that is issuing this AJAX request, jump over to my Configuration Console on my AEM publish instance, locate the Adobe Granite Cross-Origin Resource Sharing Policy which I configure and create a new configuration. Under Allowed Origins, I’ll add my www.example.com:8000 which is where the AJAX requests are originating from. I can also add an Allowed path so that only content under /content/we-retail allows CORS-based access, or reduce the Allowed Methods to only GET since that is the only method that I am using in my Javascript. And lastly, I’ll uncheck Supports Credentials since I’m only making anonymous requests. I’ll Save these changes and you can see that we have a new policy created. Let’s jump back to our example.com page.
We’ll clear our Console and Network and issue the request again and, once again, it appears that it didn’t work. So, let’s take a look at why this might be. Let’s look at our request. In our Response Headers, we don’t have our Access Control Allow Origin header present. The reason for this is because we’re accessing AEM Publish through Dispatcher. And since this is an anonymous request to JSON, it’s cacheable. So, to resolve this, we must add a Headers configuration to allow the relevant access control headers to be passed through, as well as cached, at Dispatcher. So for this, I’ll add all of the access control headers that CORS uses. We can Save our changes and restart our web server.
I’ve cleared our cache for good measure.
Let’s jump back to our example.com page, then clear Consoles and network traffic and re-request the page. Now you can see that Experience, which is the title of the page we’re requesting, is being collected and injected into the DOM. If we look at the Response Headers, we can see we have our Access Control Allowed Origin set.
If we go back into our Dispatcher cache, we can see that we’re caching the actual Response Headers as well, so subsequent requests also work.
Keep in mind that all the normal Dispatcher caching considerations apply, so make sure you’re only caching headers where those headers will be identical for any user accessing that resource. If this is not the case, the request should not be cacheable. Also, remember if caching of CORS headers is enabled, you’ll need to invalidate the appropriate pieces of the cache whenever a CORS policy configuration changes.

In this video:

  • www.example.com maps to localhost via /etc/hosts
  • aem-publish.local maps to localhost via /etc/hosts
  • SimpleHTTPServer (a wrapper for Python’s SimpleHTTPServer) is serving the HTML page via port 8000.
    • No longer available in Mac App Store. Use similar such as Jeeves.
  • AEM Dispatcher is running on Apache HTTP Web Server 2.4 and reverse-proxying request to aem-publish.local to localhost:4503.

For more details, review Understanding Cross-Origin Resource Sharing (CORS) in AEM.

www.example.com HTML and JavaScript

This Web page has logic that

  1. Upon clicking the button
  2. Makes an AJAX GET request to http://aem-publish.local/content/we-retail/.../experience/_jcr_content.1.json
  3. Retrieves the jcr:title form the JSON response
  4. Injects the jcr:title into the DOM
<html>
<head>
<script
  src="https://code.jquery.com/jquery-3.2.1.min.js"
  integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
  crossorigin="anonymous"></script>
</head>
<body style="width: 960px; margin: 2rem auto; font-size: 2rem;">
    <button style="font-size: 2rem;"
            data="fn-getTitle">Get Title as JSON from AEM</button>
    <pre id="title">The page title AJAX'd in from AEM will injected here</pre>

    <script>
    $(function() {

        /** Get Title as JSON **/
        $('body').on('click', '[data="fn-getTitle"]', function(e) {
            $.get('http://aem-publish.local/content/we-retail/us/en/experience/_jcr_content.1.json', function(data) {
                $('#title').text(data['jcr:title']);
            },'json');

            e.preventDefault();
            return false;
        });
    });
    </script>
</body>
</html>

OSGi factory configuration

The OSGi Configuration factory for Cross-Origin Resource Sharing is available via:

  • http://<host>:<port>/system/console/configMgr > Adobe Granite Cross-Origin Resource Sharing Policy
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="sling:OsgiConfig"
    alloworigin="[https://www.example.com:8000]"
    alloworiginregexp="[]"
    allowedpaths="[/content/we-retail/.*]"
    exposedheaders="[]"
    maxage="{Long}1800"
    supportedheaders="[Origin,Accept,X-Requested-With,Content-Type,
Access-Control-Request-Method,Access-Control-Request-Headers]"
    supportedmethods="[GET]"
    supportscredentials="{Boolean}false"
/>

Dispatcher configuration dispatcher-configuration

Allowing CORS request headers

To allow the required HTTP request headers to passthrough to AEM for processing, they must be allowed in the Disaptcher’s /clientheaders configuration.

/clientheaders {
   ...
   "Origin"
   "Access-Control-Request-Method"
   "Access-Control-Request-Headers"
}

Caching CORS resposne headers

To allow the caching and serving of CORS headers on cached content, add following /cache /headers configuration to the AEM Publish dispatcher.any file.

/publishfarm {
    ...
    /cache {
        ...
        # CORS HTTP response headers
        # https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#the_http_response_headers
        /headers {
            ...
            "Access-Control-Allow-Origin"
            "Access-Control-Expose-Headers"
            "Access-Control-Max-Age"
            "Access-Control-Allow-Credentials"
            "Access-Control-Allow-Methods"
            "Access-Control-Allow-Headers"
        }
    ...
    }
...
}

Restart the web server application after making changes to the dispatcher.any file.

It is likely clearing the cache entirely is required to ensure headers are appropriately cached on the next request after a /cache /headers configuration update.

Supporting materials supporting-materials

recommendation-more-help
c92bdb17-1e49-4e76-bcdd-89e4f85f45e6