Help! CORS won’t work with my AEM servlet

I ran across a very challenging issue recently, but it turned out to be simple to resolve. We were building out a small servlet to proxy requests to an API while hiding the API keys from the front end, but Chrome would always fail with a CORS error when testing locally from our React app. Once the CORS error was fixed, we constantly saw 403 forbidden despite sending the correct cookies in the headers.

Here’s how to solve this problem.

Implementing CORS

First, we need to implement basic CORS. This means sending back the correct headers and responding to OPTIONS requests from the browser. The browser often queries OPTIONS to check with the server that the request it’s about to make is allowed. This is called a preflight request.

Allowing CORS in AEM 6.3+ is actually very simple, simply find Adobe Granite Cross-Origin Resource Sharing Policy in your OSGi configurations in the AEM console. You will need to add a configuration with the host making the call. In my case, this is http://localhost:3000. Here’s what that looks like.

If this configuration already exists, you can simply edit it instead. This may be all you need to do to allow CORS in production, however, if you’re trying to test CORS from a local application against a local AEM instance, you’ll need to edit the filters as well.

Turning off the filters

In AEM, there are filters that apply to requests coming from external sources (non-AEM). These need to be disabled as they can prevent requests from ever reaching a servlet, even if the servlet is set up correctly and CORS is enabled.

The two main filters that apply here are Adobe Granite CSRF Filter and Apache Sling Referrer Filter. Find both of these in the configurations, edit them, and remove POST from “Filter Methods”.

Sending access control headers in the servlet

The servlet only needs to send a header for the POST method that the request is authorized, the CORS configuration will handle OPTIONS. This is what that change looks like:

@Component(service = Servlet.class, immediate = true, property = {
    Constants.SERVICE_DESCRIPTION + "=Your description",
    "sling.servlet.paths=/services/yourPath", 
    "sling.servlet.methods=" + HttpConstants.METHOD_POST
})
public class YourServletName extends SlingAllMethodsServlet {

    private static final Logger LOGGER = LoggerFactory.getLogger(YourServletName.class);

    private void setAllowOrigin(SlingHttpServletRequest req, SlingHttpServletResponse resp){
        String fullRequestUrl = req.getRequestURL().toString();
        String rootOrigin = fullRequestUrl.split("[:]\\d+")[0];
        if(rootOrigin.contains("localhost")){
            resp.setHeader("Access-Control-Allow-Origin", String.format("%s:3000", rootOrigin));
        }
    }

    @Override
    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {

        setAllowOrigin(request, response);
        // ... your code here
    }
}

This handles allowing only requests from localhost:3000 (a React app). If you want to allow all origins, simply set the header to *. If you want to allow a specific origin, then set it to that origin, including http/https at the beggining depending on which is being used.

It’s still not working?

If you’re still experiencing issues with the request like a 403 error response code, then ensure that the cookies are being sent properly and that you’re logged into AEM. You’ll want to check the HTTP request for a “Cookie” header. Using axios for instance, you have to specify the option withCredentials: true.

If that doesn’t fix the issue, it’s likely a problem with your servlet configuration, not with CORS or the filters. You’ll want to verify that the Access-Control-* headers are coming back with the correct values in both the preflight and the actual request to the servlet.

Leave a comment

Design a site like this with WordPress.com
Get started