457

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.example:8080/ to https://www.mywebsite.example/*

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

//*.mywebsite.example:*/*

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Elie
  • 6,915
  • 7
  • 31
  • 35

15 Answers15

360

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.

Yves M.
  • 29,855
  • 23
  • 108
  • 144
monsur
  • 45,581
  • 16
  • 101
  • 95
  • 1
    What happens if you set it to `null`? – Dexter Feb 27 '14 at 09:52
  • 9
    @Dexter "null" can be used in response to a "null" origin, e.g. when making a CORS request from a file:// scheme. – monsur Mar 03 '14 at 04:16
  • 278
    It's profoundly shortsighted that the CORS spec would not support the OP's _exact_ use-case. – aroth Oct 08 '14 at 00:55
  • 12
    @aroth: Not really, the spec allows implementations to use *any* matching syntax they want; it would only be profoundly shortsighted for an *implementation* not to support this use-case. In other words, what you specify to your server is not the ACAO value, the latter is just a protocol detail. My assumption is that there is a security scenario that requires or benefits from the origin being echoed back, but a naive implementation works by just saying "OK" or not. – tne Nov 12 '14 at 09:40
  • Is it good practice to use origin or Referer header value at server side to filter domain for same problem? – Harsh kurra Jul 10 '15 at 13:38
  • 6
    Update from 2015: This answer should be considered harmful, since it is incomplete and may lead to caching issues. Please see my answer below for a proper implementation (for Apache) and explanation: http://stackoverflow.com/a/27990162/357774 . Also, @aroth, as tne points out, the spec actually _does_ allow the OP's exact use case: http://www.w3.org/TR/cors/#resource-implementation . And as this answer points out, it's up to the server to implement it. This can be done in 3 lines, as seen in the answer referred to above. – Noyo Aug 26 '15 at 13:35
  • 45
    @Noyo - I'll clarify my original meaning then. It's profoundly shortsighted that the CORS spec does not _strictly require_ all servers that implement CORS to provide automatic, built-in support for the OP's _exact_ use-case. Leaving it up to each individual user to build their own shim using custom PHP code, rewrite rules, or what-have-you is a recipe for fragmentation, bugs, and disaster. Server devs should know better than that; and if they don't, the CORS spec should force them to. – aroth Aug 27 '15 at 00:47
  • 3
    Instead of "exact domain" it should be "origin", because it's a combination of protocol + domain + port. – Daniel W. Jun 16 '16 at 14:05
  • 1
    Adding my 2 x 0.01€, some pages are static and cannot regex the origin ; in this case the CORS subdomains ( ports... ) support would be helpful. (and not all web devs can change the web server config (nginx, apache...)) – Déjà vu May 04 '21 at 07:58
  • @tne The spec linked in this answer doesn't allow any wildcard beyond a standalone one, so what do you mean? Also, I don't get why people here talk about servers. If clients aren't required to understand more wildcards, who cares about server implementations? – John Jun 10 '22 at 17:20
  • 1
    @John I believe that what I meant at the time is that a client should be able to request any resource exactly, that such a pattern (using wildcards at arbitrary positions, regexes or anything else implementation-specific) can be configured on the server and the server can match the incoming URL in Origin against it, and that this server can reply with the _original_ Origin value in the ACAO header to signal to the client user-agent that they should let the initially untrusted client code access the response (or retry the request if it wasn't idempotent and required a preflight request). – tne Jun 30 '22 at 16:37
  • @John As you can tell in that scheme which AFAIU is absolutely OK by the spec, the client indeed doesn't have to understand more complicated patterns (only the server), and yet it still seems to work for OP's scenario (as this answer suggests). – tne Jun 30 '22 at 16:40
  • @tne Ok, I think I get it now. That means that the more complicated pattern doesn't actually appear anywhere in the protocol, correct? – John Jul 03 '22 at 22:34
  • 1
    @John I have: confirmed the Fetch standard is indeed the canonical spec for CORS, read the section on ACAO, and I concur with your initial statement that only the original value sent by the client (to signal confirmation), the "*" value (to simplify implementation of servers that allow everything), or "null" can be used by the server in its response and nothing else. I couldn't find another section relevant to your question, nor do I personally have any reason to believe there is something else that could be relevant. So, as best as I can answer your question: that does sound correct yes! – tne Jul 08 '22 at 17:26
  • Is it possible to not expose origins on the 'allowed-origins' header ? it might have sensitive data in some cases ... – Pedro Bezanilla Aug 04 '22 at 08:43
  • @PedroBezanilla I want to say yes: only send it with the value the client provided in the request `Origin` header (and only if they are indeed to be allowed of course). Otherwise simply don't send the header in the response at all. AFAIU the whole mechanism could've been implemented with boolean signaling (e.g. header present or not, its value 1/0 or true/false) to express whether the declared origin is providing code expected to target the server. I suspect the reason it's possible to specify actual domains (and let the browser figure it out) is to allow for it to be configured statically. – tne Apr 24 '23 at 01:21
  • The entire CORS security mechanism is incredibly shortsighted. Applications are not required to send neither Origin, nor Referrer headers meaning you're only susceptible to this mess if you do try to follow it, nor are they required to initiate communication with OPTIONS. Finally, entire mechanism is circumvented by running a reverse proxy that could route your requests as needed. Whoops! – Dragas Jun 15 '23 at 09:18
270

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).

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Noyo
  • 4,874
  • 4
  • 39
  • 41
  • @Noyo How is this not working for my question http://stackoverflow.com/q/29415772/2333753 – Neve12ende12 Apr 02 '15 at 15:46
  • 2
    We had almost this (not Vary Origin) and got bad behavior when visitors jumped between multiple sub domains using the same font. The font and the access-control-origin header was also cached. I have made a little change on this one: I use "Access-Control-Allow-Origin *" if the request is from one of our allowed domains. Maybe this was solved with "Vary Origin" that we didn't have before... now added that too. – Erik Melkersson Dec 04 '15 at 11:48
  • 1
    What does the `e` do? From the line `%{CORS_ALLOW_ORIGIN}e`? – Nathan Dec 18 '15 at 21:27
  • @Nathan, it's part of the **e**nvironment variable format specifier for Header values: http://httpd.apache.org/docs/2.2/mod/mod_headers.html#header . – Noyo Dec 19 '15 at 14:00
  • 2
    Doesn't work for the principal domain 'mywebsite.com' – biology.info Apr 25 '16 at 15:43
  • @Noyo, thx to your prompt answer. I do not understand your warning ? – biology.info Apr 26 '16 at 10:47
  • Do the `https:` **`//`** slashes not need to be escaped with backslashes? Regexr flagged this when I was testing inputs, and I escaped them in my config... – Peter Gordon May 02 '16 at 16:31
  • 1
    @pgmann, there's no need to escape the `//` in this context, since the Apache conf doesn't use slash-delimited regular expressions. Regexr complains because, in that context, the slashes have special meaning as delimiters. – Noyo May 04 '16 at 13:10
  • After adding this, I still get: "Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is ''. It must be 'true' to allow credentials.". Any indication as to why that would be? – Raiden616 Sep 13 '16 at 11:33
  • Welcome to Stackoverflow. I'm not asking about credentials-related headers, I'm asking about this answer... Please see rules. – Raiden616 Sep 15 '16 at 15:43
  • @Raiden616 I don't understand the context of your question. Can you please restate it or ask it as a new question? (Just to clarify, I meant I would delete my own comment, so as not to make the comments too long.) – Noyo Sep 19 '16 at 08:28
  • What about colons and why all those spaces? – Chazy Chaz Jan 06 '17 at 14:45
  • I currently have `[EnableCors(origins: "http://localhost:64, http://mysite", headers: "*", methods: "*")]` which works fine but if I update it to `[EnableCors(origins: "http://localhost:64, http://mysite, http://mysite/subsite", headers: "*", methods: "*")]` I get a 500 error from API... I have a MVC Web API. – Si8 Mar 30 '17 at 14:30
  • Hi @Si8. I don't know much about your context, but it seems your issue is that you're specifying a URI with a subdirectory in your second example, and this isn't a valid HTTP origin. Please check out [the spec](https://fetch.spec.whatwg.org/#origin-header). – Noyo Apr 01 '17 at 22:34
  • One is a sharepoint site and the other is a sharepoint subsite... When called from the subsite there is no header that is sent for some reason. Any idea? – Si8 Apr 02 '17 at 06:34
  • 1
    `The 'Access-Control-Allow-Origin' header contains multiple values '^(https?://(?:.+.)?aerofotea.com(?::d{1,5})?)$', but only one is allowed. Origin 'http://local.aerofotea.com' is therefore not allowed access.` – Aero Wang Jun 10 '17 at 11:04
  • 13
    Where do you place this code? .htaccess or in the apache virtual host config? – Glen Mar 29 '19 at 18:54
  • you saved my day thanks, it allowing to access assets of main domain into subdomain – Hitesh Jangid Aug 16 '20 at 08:16
  • It worked as it is, without any glitch. Can you share how to add X-FRAME-OPTIONS to allow parent and subdomains. – u tyagi Dec 15 '20 at 04:58
66

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.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
DaveRandom
  • 87,921
  • 11
  • 154
  • 174
50

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\.example(:\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
Dmitry Shvedov
  • 3,169
  • 4
  • 39
  • 51
Pratap Koritala
  • 1,557
  • 14
  • 16
  • I tried this CORS header workaround for some content in an `iFrame` ... and it is not working. I guess I will have to pull the content using `ajax`. – MeSo2 Nov 18 '22 at 16:27
  • Worth mentioning: This answer addresses an Apache HTTP server's `.htaccess` file. – OfirD Apr 19 '23 at 13:41
17

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'),
DaveRandom
  • 87,921
  • 11
  • 154
  • 174
Lars
  • 5,757
  • 4
  • 25
  • 55
  • 1
    +1, edited to use `preg_quote()` because that's the correct way to do it (even though `.` is the only regexp meta char valid in a DNS name, `preg_quote()` describes the intended operation better) – DaveRandom Jul 20 '15 at 00:13
  • 1
    It should be clarified that `none` is not a semantically valid value for the header (or at least, does not do what it implies) according to the spec. As such, `return null;` may make more sense for that branch, and in that case no header should be sent to the client, so it should be checked for by the caller. – DaveRandom Jul 20 '15 at 00:19
  • 2
    `preg_quote()` will quote the * sign and so the `str_replace()` for example leaves an orphaned "\". – Christoffer Bubach Nov 29 '16 at 16:37
  • 1
    this is useful, I spent time on CORS issue until I realised my site had "www" in the ajax, but not in the permalink structure - your solution helped me understand where the issue was and solved that for me. – SolaceBeforeDawn Jun 30 '18 at 03:22
4

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

SetEnvIf Origin "http(s)?://(.+\.)?domain\.example(:\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 Stack Overflow, 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\.example(:\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]
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
AamirR
  • 11,672
  • 4
  • 59
  • 73
4

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

RiZKiT
  • 2,107
  • 28
  • 23
2

We were having similar issues with Font Awesome on a static "cookie-less" domain when reading fonts from the "cookie domain" (www.domain.example) and this post was our hero. See here: How can I fix the 'Missing Cross-Origin Resource Sharing (CORS) Response Header' webfont issue?

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\.example|mywebsite\.example)(:\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.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
K8sN0v1c3
  • 109
  • 1
  • 10
2

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.example.

SetEnvIf Host ^((?:.+\.)*yourcompany\.example?)$    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"
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Jack K
  • 21
  • 1
1

For Spring, you should use allowedOriginPatterns, it does exactly what you want

Alternative to setAllowedOrigins(java.util.List<java.lang.String>) that supports more flexible origins patterns with "*" anywhere in the host name in addition to port lists. Examples:

https://*.domain1.com -- domains ending with domain1.com
https://*.domain1.com:[8080,8081] -- domains ending with domain1.com on port 8080 or port 8081
https://*.domain1.com:[*] -- domains ending with domain1.com on any port, including the default port 

In contrast to allowedOrigins which only supports "" and cannot be used with allowCredentials, when an allowedOriginPattern is matched, the Access-Control-Allow-Origin response header is set to the matched origin and not to "" nor to the pattern. Therefore allowedOriginPatterns can be used in combination with setAllowCredentials(java.lang.Boolean) set to true.

For example:

@Bean
public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            List<String> allowedOrigins = List.of("https://*.example.com", "http://*.example2.com[80,8080]");
            registry.addMapping("/**").allowedOriginPatterns(allowedOrigins.toArray(new String[0]));

        }
    };
}
dan carter
  • 4,158
  • 1
  • 33
  • 34
0

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']));
    }
kevnk
  • 18,733
  • 3
  • 28
  • 30
Capricorn
  • 2,061
  • 5
  • 24
  • 31
0

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.

kevnk
  • 18,733
  • 3
  • 28
  • 30
seaders
  • 3,878
  • 3
  • 40
  • 64
0

I was having trouble with the accepted answer. A minor tweak using Header always set allowed constant behavior. Please note the example below is effectively a wildcard solution and can lead to unintended behavior, such as users being will be able to hit your (potentially) SSL-enabled site from their locally hosted envs. I would NOT advise this behavior for production webservers, but everyone's use case is different, so with the disclaimer, I feel it is beneficial for non-production. You can use the other examples for more tailored instructions.

# https://stackoverflow.com/questions/14003332/access-control-allow-origin-wildcard-subdomains-ports-and-protocols/27990162#27990162
SetEnvIf Origin ^(https?://.*(?::\d{1,5})?)$ CORS_ALLOW_ORIGIN=$1
Header always set Access-Control-Allow-Origin %{CORS_ALLOW_ORIGIN}e env=CORS_ALLOW_ORIGIN
Header merge Vary "Origin"
# todo - Cached for a day - 86400 
Header always set Access-Control-Max-Age: 0
Header always set Access-Control-Allow-Methods "GET, POST, PATCH, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers: *
0

Even easier you can use the @CrossOrigin annotation with the originPatterns attribute set to what you need, it supports wildcards and it works with allowedCredentials set to true.

JediCate
  • 396
  • 4
  • 8
-4

in my case using angular

in my HTTP interceptor , i set

with Credentials: true.

in the header of the request

hosam hemaily
  • 412
  • 5
  • 17