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

Cors

Cors Problem Overview


I'm trying to enable CORS for all subdomains, ports and protocol.

For example, I want to be able to run an XHR request from http://sub.mywebsite.com:8080/ to https://www.mywebsite.com/*

Typically, I'd like to enable request from origins matching (and limited to):

//*.mywebsite.com:*/*

Cors Solutions


Solution 1 - Cors

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.

Solution 2 - Cors

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\.com(?::\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.com) in addition to all its subdomains can simply replace the regular expression in the first line with this one:

^(https?://(?:.+\.)?mywebsite\.com(?::\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).

Solution 3 - Cors

EDIT: 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.com

    # 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.com as the ACCESS_CONTROL_ROOT and it will limit origins to sub.mydomain.com and *.sub.mydomain.com (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.

Solution 4 - Cors

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.com while works for http://somedomain.mywebsite.com/

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.com" in the above Apache Configuration.

To allow Multiple sites:

SetEnvIf Origin "http(s)?://(.+\.)?(othersite\.com|mywebsite\.com)(:\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://examplesite1.com" --verbose http://examplesite2.com/query

Solution 5 - Cors

I needed a PHP-only solution, so just in case someone needs it as well. It takes an allowed input string like "*.example.com" and returns the request header server name, if the input matches.

function getCORSHeaderOrigin($allowed, $input)
{
	if ($allowed == '*') {
		return '*';
    }

	$allowed = preg_quote($allowed, '/');

	if (($wildcardPos = strpos($allowed, '*')) !== false) {
		$allowed = str_replace('*', '(.*)', $allowed);
	}

	$regexp = '/^' . $allowed . '$/';

	if (!preg_match($regexp, $input, $matches)) {
		return 'none';
    }

	return $input;
}

And here are the test cases for a phpunit data provider:

//    <description>                            <allowed>          <input>                   <expected>
array('Allow Subdomain',                       'www.example.com', 'www.example.com',        'www.example.com'),
array('Disallow wrong Subdomain',              'www.example.com', 'ws.example.com',         'none'),
array('Allow All',                             '*',               'ws.example.com',         '*'),
array('Allow Subdomain Wildcard',              '*.example.com',   'ws.example.com',         'ws.example.com'),
array('Disallow Wrong Subdomain no Wildcard',  '*.example.com',   'example.com',            'none'),
array('Allow Double Subdomain for Wildcard',   '*.example.com',   'a.b.example.com',        'a.b.example.com'),
array('Don\'t fall for incorrect position',    '*.example.com',   'a.example.com.evil.com', 'none'),
array('Allow Subdomain in the middle',         'a.*.example.com', 'a.bc.example.com',       'a.bc.example.com'),
array('Disallow wrong Subdomain',              'a.*.example.com', 'b.bc.example.com',       'none'),
array('Correctly handle dots in allowed',      'example.com',     'exampleXcom',            'none'),

Solution 6 - Cors

When setting Access-Control-Allow-Origin in .htaccess, only following worked:

SetEnvIf Origin "http(s)?://(.+\.)?domain\.com(:\d{1,5})?$" CRS=$0
Header always set Access-Control-Allow-Origin "%{CRS}e" env=CRS

I tried several other suggested keywords Header append, Header set, none worked as suggested in many answers on SO, though I have no idea if these keywords are outdated or not valid for nginx.

Here is my complete solution:

SetEnvIf Origin "http(s)?://(.+\.)?domain\.com(:\d{1,5})?$" CRS=$0
Header always set Access-Control-Allow-Origin "%{CRS}e" env=CRS
Header merge Vary "Origin"

Header always set Access-Control-Allow-Methods "GET, POST"
Header always set Access-Control-Allow-Headers: *

# Cached for a day
Header always set Access-Control-Max-Age: 86400

RewriteEngine On

# Respond with 200OK for OPTIONS
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]

Solution 7 - Cors

For Spring Boot I found this RegexCorsConfiguration which extends the official CorsConfiguration: https://github.com/looorent/spring-security-jwt/blob/master/src/main/java/be/looorent/security/jwt/RegexCorsConfiguration.java

Solution 8 - Cors

We were having similar issues with Font Awesome on a static "cookie-less" domain when reading fonts from the "cookie domain" (www.domain.tld) and this post was our hero. See here: https://stackoverflow.com/questions/27009425/how-can-i-fix-the-missing-cross-origin-resource-sharing-cors-response-header/47369393#47369393

For the copy/paste-r types (and to give some props) I pieced this together from all the contributions and added it to the top of the .htaccess file of the site root:

<IfModule mod_headers.c>
 <IfModule mod_rewrite.c>
    SetEnvIf Origin "http(s)?://(.+\.)?(othersite\.com|mywebsite\.com)(:\d{1,5})?$" CORS=$0
    Header set Access-Control-Allow-Origin "%{CORS}e" env=CORS
    Header merge  Vary "Origin"
 </IfModule>
</IfModule>

Super Secure, Super Elegant. Love it: You don't have to open up your servers bandwidth to resource thieves / hot-link-er types.

Solution 9 - Cors

It looks like the original answer was for pre Apache 2.4. It did not work for me. Here's what I had to change to make it work in 2.4. This will work for any depth of subdomain of yourcompany.com.

SetEnvIf Host ^((?:.+\.)*yourcompany\.com?)$    CORS_ALLOW_ORIGIN=$1
Header append Access-Control-Allow-Origin  %{REQUEST_SCHEME}e://%{CORS_ALLOW_ORIGIN}e    env=CORS_ALLOW_ORIGIN
Header merge  Vary "Origin"

Solution 10 - Cors

I had to modify Lars' answer a bit, as an orphaned \ ended up in the regex, to only compare the actual host (not paying attention to the protocol or port) and I wanted to support localhost domain besides my production domain. Thus I changed the $allowed parameter to be an array.

function getCORSHeaderOrigin($allowed, $input)
{
    if ($allowed == '*') {
        return '*';
    }

    if (!is_array($allowed)) {
        $allowed = array($allowed);
    }

    foreach ($allowed as &$value) {
        $value = preg_quote($value, '/');

        if (($wildcardPos = strpos($value, '\*')) !== false) {
            $value = str_replace('\*', '(.*)', $value);
        }
    }

    $regexp = '/^(' . implode('|', $allowed) . ')$/';

    $inputHost = parse_url($input, PHP_URL_HOST);

    if ($inputHost === null || !preg_match($regexp, $inputHost, $matches)) {
        return 'none';
    }

    return $input;
}

Usage as follows:

if (isset($_SERVER['HTTP_ORIGIN'])) {
    header("Access-Control-Allow-Origin: " . getCORSHeaderOrigin(array("*.myproduction.com", "localhost"), $_SERVER['HTTP_ORIGIN']));
}

Solution 11 - Cors

For me, I wanted a multi-domain option, and this is the solution I use, with python and flask,

VALID_DOMAINS = 'https://subdomain1.example.com', 'https://subdomain2.example.com'


def handle_request(request):
    origin = request.headers.get('Origin')

    if request.method == 'OPTIONS':
        if origin not in :
            return ''

        headers = {
            'Access-Control-Allow-Origin': origin,
            'Access-Control-Allow-Methods': 'GET',
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Max-Age': '3600',
        }
        return '', 204, headers

    return (
        main_function_with_logic(request),
        200,
        {'Access-Control-Allow-Origin': origin,
         ...}
    )

You can obviously expand the VALID_DOMAINS to however long you want, and whatever you want (non-https, different port, etc etc), and just check it on the request.

I prefer this solution than a wildcard solution, so this is my choice on the servers I run.

Solution 12 - Cors

in my case using angular

in my HTTP interceptor , i set

with Credentials: true.

in the header of the request

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionElieView Question on Stackoverflow
Solution 1 - CorsmonsurView Answer on Stackoverflow
Solution 2 - CorsNoyoView Answer on Stackoverflow
Solution 3 - CorsDaveRandomView Answer on Stackoverflow
Solution 4 - CorsPratap KoritalaView Answer on Stackoverflow
Solution 5 - CorsLarsView Answer on Stackoverflow
Solution 6 - CorsAamirRView Answer on Stackoverflow
Solution 7 - CorsRiZKiTView Answer on Stackoverflow
Solution 8 - CorsK8sN0v1c3View Answer on Stackoverflow
Solution 9 - CorsJack KView Answer on Stackoverflow
Solution 10 - CorsCapricornView Answer on Stackoverflow
Solution 11 - CorsseadersView Answer on Stackoverflow
Solution 12 - Corshosam hemailyView Answer on Stackoverflow