Cross-Origin Resource Sharing (CORS)

If you’re seeing any of these things, there’s a good chance that you are experiencing a cross-origin resource failure:

In short, web browsers don’t allow sites to make requests to origins that differ from the one it is accessing. Developers are most affected by this restriction with web fonts and Ajax requests. The “origin” includes both the protocol (http://, https://) and the domain. It may not be immediately obvious that all these are different origins:

How do you know that you’re experiencing a problem with cross-origin resource sharing? If you open your developer tools in your browser, the console will have logged an error. The wording differs based on the browser, but typically will mention “Access-Control-Allow-Origin” somewhere in the message.

Notice in the above examples that a domain with and without www are two separate origins. If this is the reason you’re experiencing a CORS issue, it is best solved by using a simple canonical redirect to either remove or force www in your URLs. This is best practice even if you aren’t having an issue with cross-origin resource sharing.

Depending on the scenario you are facing, we will use a slightly different solution. In every case, we are adding an appropriate Access-Control-Allow-Origin header to the request. We use * if we don’t care about the requesting domain, or a specific origin if we need tighter control.

Web Fonts Won’t Load

Let’s say you have a @font-face declaration in your CSS, but the font is on a different origin. In that case, we will whitelist cross-origin access just for font files. In the folder where the font files live, add a .htaccess file with the following:

# Allow font assets to be used across domains and subdomains
<FilesMatch "\.(ttf|otf|eot|woff)$">
  <IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "*"
  </IfModule>
</FilesMatch>

JavaScript Errors in the Control Panel

If you’re getting JavaScript errors in your ExpressionEngine control panel, and upon inspection see that something is not loaded due to cross-origin policy, you’ll need to add a header covering those resources. In this scenario:

In the user/themes/example/ folder add a .htaccess file with the following:

# Allow access to these theme files from https://admin.example.com
<IfModule mod_headers.c>
  Header set Access-Control-Allow-Origin "https://admin.example.com"
</IfModule>

Note that we don’t have to specify https://example.com, because browsers will always allow access to resources from the same origin they live on.

Front-end Ajax Endpoint Won’t Load

In this scenario, you have a template that you’ve set up to act as an Ajax endpoint. We’ll assume it’s a public resource, so we will allow * origins.

We have two different ways to do this. One is with server config, like the .htaccess we’ve used so far, and the other is to control it within your ExpressionEngine template itself. In both cases, we’re assuming a URL of https://example.com/ajax/endpoint.

Using .htaccess

In the web root’s .htaccess, add:

# Allow cross-domain access to our Ajax endpoint
<IfModule mod_headers.c>
  SetEnvIf Request_URI "/ajax/endpoint" CORS=True
  Header set Access-Control-Allow-Origin "*" env=CORS
</IfModule>

The first line sets an environment variable named CORS, but only for our specific URI. The second line sets the Access-Control-Allow-Origin header as normal, but the addition of env=CORS means that it will only set the header when that environment variable is set.

This method is effective whether ExpressionEngine manages the resource, and allows regular expression URL patterns much as you’d use RewriteCond %{REQUEST_URI} with mod_rewrite.

In an ExpressionEngine Template

You may prefer for the cross-origin access to be set in the ExpressionEngine template itself, so another developer (including your future self) doesn’t have to hunt around to figure out how and where the header is set, or why. In this case, you can use a simple plugin to add cross-origin resource sharing to any template: Cross-Origin-Headers (plugin on GitHub).

{exp:cross_origin_headers}

This will allow * origins to access the content. Or to allow only a unique foreign origin:

{exp:cross_origin_headers domain='https://subdomain.example.com'}

Behind the scenes this extremely lightweight plugin sets the Access-Control-Allow-Origin header for us with PHP.

Allowing Multiple Domain Origins

One complication of the CORS implementation is that it only allows a resource to either be made fully public, or allowed to a single foreign origin. What if you have many domains (including subdomains) that need to share secure resources? You can accomplish this with server configuration, but it does have a caveat. In the folder that includes the resources that need to be shared, add the following .htaccess:

# whitelist domains to allow CORS between, including http/https
<IfModule mod_headers.c>
    SetEnvIf Origin "http(s)?://(example.com|subdomain.example.com)$" AccessControlAllowOrigin=$0
    Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
</IfModule>

Just replace example.com|subdomain.example.com in the above with a pipe-delimited list of the domains you need to allow. The first line sets an environment variable, only if the origin is in our list. The second line sets our header with the domain that we matched, only when the environment variable is set.

What’s the caveat? Origin is a request header, and thus cannot be trusted. It works in all major browsers, so it is convenient and adds a modest amount of protection compared with *. But, a malicious user could violate your cross-origin policies by faking the Origin header. If you use this method on secure resources, make sure that they are adequately protected with server/user authentication, and do not rely soley on cross-origin browser policies.

Further Reading

If this topic is of interest to you, or you need to dive more deeply than the examples provided, here are some more resources for you.

Browser Error Message Reference

The following is a list of error messages from major browser vendors related to CORS. I’ve included it here to help you identify if that’s the issue you’re having, and to hopefully expose this article to people searching for help with these error messages.

Derek Jones's avatar
Derek Jones

Derek has been making software since age six, crossing over into video and music production, graphic design, and even retail management before helping build ExpressionEngine at EllisLab.

Comments 0

Be the first to comment!