Friday, August 17, 2018

How to Enable Cross Origin Resource Sharing(CORS) support in AEM(Adobe Experience Manager)

How to Enable Cross Origin Resource Sharing(CORS) support in AEM(Adobe Experience Manager)


Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served.

Sometimes we may have scenarios to expose the AEM services to be invoked from cross origins, this post explains the different approaches to enable the Cross Origin Resource Sharing Supports.

The CORS support is introduced OOB in AEM 6.3 version, refer https://helpx.adobe.com/experience-manager/kt/platform-repository/using/cors-security-article-understand.html for more details.


Enabling CORS for older AEM versions(<6.3)


The request is failed with below exception in browser console while trying to invoke cross origin(service hosted in different domains) services through Ajax.

"Failed to load http://localhost:4503/bin/sampleJSONPService: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access".

There are two approaches to enable the Cross Origin Requests

  • JSONP
  • CORS Headers

JSONP


JSONP stands for JSON with Padding, JSONP is a method for sending JSON data without worrying about cross-domain issues.
JSONP supports only the GET request method, JSONP works on legacy browsers.

Enable the service to rerun the JSON response with Padding


@SuppressWarnings("serial")
@SlingServlet(
   selectors = "json",
   paths = "/bin/sampleJSONPService",
   methods = "GET")
public class SimpleServlet extends SlingSafeMethodsServlet {

    @Override
    protected void doGet(final SlingHttpServletRequest req,
            final SlingHttpServletResponse resp) throws ServletException, IOException {
    resp.setHeader("Content-Type", "application/json");
    resp.setHeader("X-Content-Type-Options", "");
    String callback=req.getParameter("callback");
       
    JSONObject response = new JSONObject();
    try {
response.put("test1", "testvalue1");
response.put("test2", "testvalue2");
response.put("test3", "testvalue3");
response.put("test4", "testvalue4");
} catch (JSONException e) {
e.printStackTrace();
}
   
    if(callback!=null && !callback.trim().equals("")) {
    resp.getWriter().write( callback + "("+response.toString()+ ")");
    }else
    {
    resp.getWriter().write(response.toString());
    }   
   
    }
}

The above service return the JSON response with padding

Invoke the JSONP service from Ajax


<html>
<head>
<script
  src="https://code.jquery.com/jquery-3.3.1.min.js"  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="  crossorigin="anonymous">
</script>
</head>
<body>

Test Data (<span class="testData">Default</span>)

<script>
var dataUrl = 'http://localhost:4503/bin/sampleJSONPService.json?callback=?';

$.ajax({
url: dataUrl,
dataType: 'jsonp',
success: function(data) {
jQuery('.testData').html('').text(data.test1)
}});
</script>
</body>
</html>


This will provide the response without any issue

test({"test1":"testvalue1","test2":"testvalue2","test3":"testvalue3","test4":"testvalue4"})


CORS Headers


Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. CORS also supports other types(other than GET) of HTTP requests, CORS is supported by most modern web browsers.

The Access-Control-Allow-Origin response header indicates whether the response can be shared with resources with the given origin.

e.g.
Access-Control-Allow-Origin "*"   supports the Cross Origin requests from all domains
Access-Control-Allow-Origin "https://example.com" supports Cross Origin requests only from https://example.com

Enabling Access-Control-Allow-Origin header


The header can be enabled directly in the service response or thorough apache configuration

Though Service response - Add Access-Control-Allow-Origin header with required value in the service response


@SuppressWarnings("serial")
@SlingServlet(
   selectors = "json",
   paths = "/bin/sampleJSONPService",
   methods = "GET")
public class SimpleServlet extends SlingSafeMethodsServlet {
    @Override
    protected void doGet(final SlingHttpServletRequest req,
            final SlingHttpServletResponse resp) throws ServletException, IOException {

        resp.setHeader("Content-Type", "application/json");
    resp.setHeader("X-Content-Type-Options", "");
    resp.setHeader("Access-Control-Allow-Origin", "*"); 
       
    JSONObject response = new JSONObject();
    try {
response.put("test1", "testvalue1");
response.put("test2", "testvalue2");
response.put("test3", "testvalue3");
response.put("test4", "testvalue4");
} catch (JSONException e) {
e.printStackTrace();
}
       
      resp.getWriter().write(response.toString());   
    }
}

Enabling the header through Apache


Add the below header in virtual host, this will add the Access-Control-Allow-Origin header to all the responses

Header always set Access-Control-Allow-Origin "*"

Restricting the header to specific URL responses

SetEnvIf Request_URI ^/bin/sampleJSONPService is_cors
Header always set Access-Control-Allow-Origin "*" env=is_cors

The mod_headers.so module should be enabled to support this
LoadModule headers_module modules/mod_headers.so

Invoke the Service


<html>
<head>
<script
  src="https://code.jquery.com/jquery-3.3.1.min.js"  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="  crossorigin="anonymous">
</script>
</head>
<body>

Test Data (<span class="testData">Default</span>)

<script>
var dataUrl = 'http://localhost:4503/bin/sampleJSONPService.json';

$.ajax({
url: dataUrl,
dataType: 'json',
success: function(data) {
jQuery('.testData').html('').text(data.test1)
}});
</script>
</body>
</html>

Additional CORS headers


Access-Control-Max-Age - The Access-Control-Max-Age response header indicates how long the results of a preflight request (that is the information contained in the Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can be cached.

Access-Control-Allow-Credentials - The Access-Control-Allow-Credentials response header indicates whether or not the response to the request can be exposed to the page. It can be exposed when the true value is returned. Credentials are cookies, authorization headers or TLS client certificates.

Access-Control-Expose-Headers - The Access-Control-Expose-Headers response header indicates which headers can be exposed as part of the response by listing their names. e.g Access-Control-Expose-Headers "header1, header2, header3"

Access-Control-Request-Headers - The Access-Control-Request-Headers request header is used when issuing a preflight request to let the server know which HTTP headers will be used when the actual request is made. e.g. Access-Control-Request-Headers "X-Requested-With, Origin"

Access-Control-Request-Method -  The Access-Control-Request-Method request header is used when issuing a preflight request to let the server know which HTTP method will be used when the actual request is made. Access-Control-Request-Method "PUT"

Access-Control-Allow-Methods - The Access-Control-Allow-Methods response header specifies the method or methods allowed when accessing the resource in response to a preflight request. Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"

Access-Control-Allow-Headers - The Access-Control-Allow-Headers response header is used in response to a preflight request which includes the Access-Control-Request-Headers to indicate which HTTP headers can be used during the actual request. e.g. Access-Control-Allow-Headers "X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding"


If the request have implications on user data, a simple request is insufficient. Instead, a preflight CORS request is sent in advance of the actual request to ensure that the actual request is safe to send. Preflight requests are appropriate when the actual request is any HTTP Method other than GET, POST, or HEAD or if a POST requests content type is anything other than application/x-www-form-urlencoded, multipart/form-data, or text/plain. Also, if the request contains any custom headers, then a preflight request is required.

OPTIONS /test/data
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/536.30.1 (KHTML, like Gecko) Version/6.0.5 Safari/536.30.1
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: origin, x-requested-with, accept
Origin: http://example.com

The preflight request is essentially asking the server if it would allow the DELETE request, without actually sending the DELETE request. If the server allows the original request, then it will respond to the preflight request like this:

HTTP/1.1 200 OK
Date: Wed, 20 Nov 2013 19:36:00 GMT
Server: Apache-Coyote/1.1
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 86400

The response to the pre-flight request indicates (in the Access-Control-Allow-Methods header) that the client is allowed to issue a DELETE request for the given resource. The Access-Control-Max-Age indicates that this pre-flight response is good for 86,400 seconds, or 1 day, after which a new pre-flight request must be issued. In the meantime, the client will be allowed to send the original DELETE request for the resource.


2 comments: