Access-Control-Allow-Origin wildcard subdomains, ports and protocols

Use @Noyo's solution instead of this one. It's simpler, clearer and likely a lot more performant under load.

ORIGINAL ANSWER LEFT HERE FOR HISTORICAL PURPOSES ONLY!!


I did some playing around with this issue and came up with this reusable .htaccess (or httpd.conf) solution that works with Apache:

<IfModule mod_rewrite.c>
<IfModule mod_headers.c>
    # Define the root domain that is allowed
    SetEnvIf Origin .+ ACCESS_CONTROL_ROOT=yourdomain.example

    # Check that the Origin: matches the defined root domain and capture it in
    # an environment var if it does
    RewriteEngine On
    RewriteCond %{ENV:ACCESS_CONTROL_ROOT} !=""
    RewriteCond %{ENV:ACCESS_CONTROL_ORIGIN} =""
    RewriteCond %{ENV:ACCESS_CONTROL_ROOT}&%{HTTP:Origin} ^([^&]+)&(https?://(?:.+?\.)?\1(?::\d{1,5})?)$
    RewriteRule .* - [E=ACCESS_CONTROL_ORIGIN:%2]

    # Set the response header to the captured value if there was a match
    Header set Access-Control-Allow-Origin %{ACCESS_CONTROL_ORIGIN}e env=ACCESS_CONTROL_ORIGIN
</IfModule>
</IfModule>

Just set the ACCESS_CONTROL_ROOT variable at the top of the block to your root domain and it will echo the Origin: request header value back to the client in the Access-Control-Allow-Origin: response header value if it matches your domain.

Note also that you can use sub.mydomain.example as the ACCESS_CONTROL_ROOT and it will limit origins to sub.mydomain.example and *.sub.mydomain.example (i.e. it doesn't have to be the domain root). The elements that are allowed to vary (protocol, port) can be controlled by modifying the URI matching portion of the regex.


I'm answering this question, because the accepted answer can't do following

  1. regex grouping is a performance hit, which is not necessary.
  2. cannot match primary domain and it only works for sub domain.

For example: It won't send CORS headers for http://mywebsite.example while works for http://somedomain.mywebsite.example/

SetEnvIf Origin "http(s)?://(.+\.)?mywebsite\.com(:\d{1,5})?$" CORS=$0

Header set Access-Control-Allow-Origin "%{CORS}e" env=CORS
Header merge  Vary "Origin"

To enable for your site, you just put your site in place of mywebsite.example in the above Apache Configuration.

To allow Multiple sites:

SetEnvIf Origin "http(s)?://(.+\.)?(othersite\.com|mywebsite\.example)(:\d{1,5})?$" CORS=$0

Validating After deploying:

The following curl response should have the "Access-Control-Allow-Origin" header after the change.

curl -X GET -H "Origin: http://site1.example" --verbose http://site2.example/query

Based on DaveRandom's answer, I was also playing around and found a slightly simpler Apache solution that produces the same result (Access-Control-Allow-Origin is set to the current specific protocol + domain + port dynamically) without using any rewrite rules:

SetEnvIf Origin ^(https?://.+\.mywebsite\.example(?::\d{1,5})?)$   CORS_ALLOW_ORIGIN=$1
Header append Access-Control-Allow-Origin  %{CORS_ALLOW_ORIGIN}e   env=CORS_ALLOW_ORIGIN
Header merge  Vary "Origin"

And that's it.

Those who want to enable CORS on the parent domain (e.g. mywebsite.example) in addition to all its subdomains can simply replace the regular expression in the first line with this one:

^(https?://(?:.+\.)?mywebsite\.example(?::\d{1,5})?)$.

Note: For spec compliance and correct caching behavior, ALWAYS add the Vary: Origin response header for CORS-enabled resources, even for non-CORS requests and those from a disallowed origin (see example why).


The CORS spec is all-or-nothing. It only supports *, null or the exact protocol + domain + port: http://www.w3.org/TR/cors/#access-control-allow-origin-response-header

Your server will need to validate the origin header using the regex, and then you can echo the origin value in the Access-Control-Allow-Origin response header.

Tags:

Cors