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:
- Font-based icons (or any web font) not showing up
- JavaScript failing in your control panel with add-ons while using the Site Manager
- Front-end Ajax endpoint that won’t load
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:
http://example.com
https://example.com
http://www.example.com
http://subdomain.example.com
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:
- An addon named Example is attempting to make an Ajax request to the themes folder
- The control panel runs from
https://admin.example.com
- The Themes URL runs from
https://example.com
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.
- W3C Recommendation for Cross-Origin Resource Sharing
- W3C CORS for Developers, Working Group Note
- Cross-origin resource sharing, Wikipedia
- Enable CORS
- Apache SetEnvIf Directive
- Apache Headers
- CORS on Nginx
- Access-Control-Allow-Credentials
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.
- Chrome / Opera
- Access to Font at ‘https://subdomain.example.com/some-font.woff’ from origin ‘https://www.example.com’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://www.example.com’ is therefore not allowed access.
- XMLHttpRequest cannot load https://subdomain.example.com/ajax.html. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://www.example.com’ is therefore not allowed access.
- FireFox
- Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://subdomain.example.com/ajax.html. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). (unknown)
- Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://subdomain.example.com/some-font.woff. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
- downloadable font: download failed (font-family: “Some Font” style:normal weight:500 stretch:normal src index:1): bad URI or cross-site access not allowed source: https://subdomain.example.com/some-font.woff
- Safari
- XMLHttpRequest cannot load https://subdomain.example.com/ajax.html. Origin https://www.example.com is not allowed by Access-Control-Allow-Origin.
- Windows Edge
- SEC7120: Origin https://www.example.com not found in Access-Control-Allow-Origin header. cors.html
- SCRIPT7002: XMLHttpRequest: Network Error 0x80700013, Could not complete the operation due to error 80700013. cors.html
- CSS3116: @font-face failed cross-origin request. No Access-Control-Allow-Origin header. some-font.woff
Comments 0
Be the first to comment!