cors

xmlhttprequest

cross-domain

.htaccess

http

Is there a way to allow multiple cross-domains using the Access-Control-Allow-Origin header?

I'm aware of the *, but it is too open. I really want to allow just a couple domains.

As an example, something like this:

Access-Control-Allow-Origin: http://domain1.example, http://domain2.example

I have tried the above code but it does not seem to work in Firefox.

Is it possible to specify multiple domains or am I stuck with just one?

Solution 1

Sounds like the recommended way to do it is to have your server read the Origin header from the client, compare that to the list of domains you would like to allow, and if it matches, echo the value of the Origin header back to the client as the Access-Control-Allow-Origin header in the response.

With .htaccess you can do it like this:

# ----------------------------------------------------------------------
# Allow loading of external fonts
# ----------------------------------------------------------------------
<FilesMatch "\.(ttf|otf|eot|woff|woff2)$">
    <IfModule mod_headers.c>
        SetEnvIf Origin "http(s)?://(www\.)?(google.com|staging.google.com|development.google.com|otherdomain.example|dev02.otherdomain.example)$" AccessControlAllowOrigin=$0
        Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
        Header merge Vary Origin
    </IfModule>
</FilesMatch>

Solution 2

Another solution I'm using in PHP:

$http_origin = $_SERVER['HTTP_ORIGIN'];

if ($http_origin == "http://www.domain1.com" || $http_origin == "http://www.domain2.com" || $http_origin == "http://www.domain3.com")
{  
    header("Access-Control-Allow-Origin: $http_origin");
}

Solution 3

This worked for me:

SetEnvIf Origin "^http(s)?://(.+\.)?(domain\.example|domain2\.example)$" origin_is=$0 
Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is

When put in .htaccess, it will work for sure.

Solution 4

I had the same problem with woff-fonts, multiple subdomains had to have access. To allow subdomains I added something like this to my httpd.conf:

SetEnvIf Origin "^(.*\.example\.com)$" ORIGIN_SUB_DOMAIN=$1
<FilesMatch "\.woff$">
    Header set Access-Control-Allow-Origin "%{ORIGIN_SUB_DOMAIN}e" env=ORIGIN_SUB_DOMAIN
</FilesMatch>

For multiple domains you could just change the regex in SetEnvIf.

Solution 5

Here's how to echo the Origin header back if it matches your domain with Nginx, this is useful if you want to serve a font multiple sub-domains:

location /fonts {
    # this will echo back the origin header
    if ($http_origin ~ "example.org$") {
        add_header "Access-Control-Allow-Origin" $http_origin;
    }
}

Solution 6

For ExpressJS applications you can use:

app.use((req, res, next) => {
    const corsWhitelist = [
        'https://domain1.example',
        'https://domain2.example',
        'https://domain3.example'
    ];
    if (corsWhitelist.indexOf(req.headers.origin) !== -1) {
        res.header('Access-Control-Allow-Origin', req.headers.origin);
        res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
    }

    next();
});

Solution 7

Here is what i did for a PHP application which is being requested by AJAX

$request_headers        = apache_request_headers();
$http_origin            = $request_headers['Origin'];
$allowed_http_origins   = array(
                            "http://myDumbDomain.example"   ,
                            "http://anotherDumbDomain.example"  ,
                            "http://localhost"  ,
                          );
if (in_array($http_origin, $allowed_http_origins)){  
    @header("Access-Control-Allow-Origin: " . $http_origin);
}

If the requesting origin is allowed by my server, return the $http_origin itself as value of the Access-Control-Allow-Origin header instead of returning a * wildcard.

Solution 8

There is one disadvantage you should be aware of: As soon as you out-source files to a CDN (or any other server which doesn't allow scripting) or if your files are cached on a proxy, altering response based on 'Origin' request header will not work.

Solution 9

For Nginx users to allow CORS for multiple domains. I like the @marshall's example although his anwers only matches one domain. To match a list of domain and subdomain this regex make it ease to work with fonts:

location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ {
   if ( $http_origin ~* (https?://(.+\.)?(domain1|domain2|domain3)\.(?:me|co|com)$) ) {
      add_header "Access-Control-Allow-Origin" "$http_origin";
   }
}

This will only echo "Access-Control-Allow-Origin" headers that matches with the given list of domains.

Solution 10

For multiple domains, in your .htaccess:

<IfModule mod_headers.c>
    SetEnvIf Origin "http(s)?://(www\.)?(domain1.example|domain2.example)$" AccessControlAllowOrigin=$0$1
    Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
    Header set Access-Control-Allow-Credentials true
</IfModule>

Solution 11

As mentioned above, Access-Control-Allow-Origin should be unique and Vary should be set to Origin if you are behind a CDN (Content Delivery Network).

Relevant part of my Nginx configuration:

if ($http_origin ~* (https?://.*\.mydomain.example(:[0-9]+)?)) {
  set $cors "true";
}
if ($cors = "true") {
  add_header 'Access-Control-Allow-Origin' "$http_origin";
  add_header 'X-Frame-Options' "ALLOW FROM $http_origin";
  add_header 'Access-Control-Allow-Credentials' 'true';
  add_header 'Vary' 'Origin';
}

Solution 12

For IIS 7.5+ with URL Rewrite 2.0 module installed please see this SO answer

Solution 13

Here's a solution for Java web app, based the answer from yesthatguy.

I am using Jersey REST 1.x

Configure the web.xml to be aware of Jersey REST and the CORSResponseFilter

<!-- Jersey REST config -->
<servlet>
  <servlet-name>JAX-RS Servlet</servlet-name>
  <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
  <init-param>
    <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
    <param-value>com.your.package.CORSResponseFilter</param-value>
  </init-param>
  <init-param>
    <param-name>com.sun.jersey.config.property.packages</param-name>
    <param-value>com.your.package</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>JAX-RS Servlet</servlet-name>
  <url-pattern>/ws/*</url-pattern>
</servlet-mapping>

Here's the code for CORSResponseFilter

import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;


public class CORSResponseFilter implements ContainerResponseFilter{

    @Override
    public ContainerResponse filter(ContainerRequest request,
            ContainerResponse response) {
        
        String[] allowDomain = {"http://localhost:9000","https://my.domain.example"};
        Set<String> allowedOrigins = new HashSet<String>(Arrays.asList (allowDomain));                  
        
        String originHeader = request.getHeaderValue("Origin");
        
        if(allowedOrigins.contains(originHeader)) {
            response.getHttpHeaders().add("Access-Control-Allow-Origin", originHeader);
                        
            response.getHttpHeaders().add("Access-Control-Allow-Headers",
                    "origin, content-type, accept, authorization");
            response.getHttpHeaders().add("Access-Control-Allow-Credentials", "true");
            response.getHttpHeaders().add("Access-Control-Allow-Methods",
                    "GET, POST, PUT, DELETE, OPTIONS, HEAD");
        }
        
        return response;
    }
}

Solution 14

Maybe I am wrong, but as far as I can see Access-Control-Allow-Origin has an "origin-list" as parameter.

By definition an origin-list is:

origin            = "origin" ":" 1*WSP [ "null" / origin-list ]
origin-list       = serialized-origin *( 1*WSP serialized-origin )
serialized-origin = scheme "://" host [ ":" port ]
                  ; <scheme>, <host>, <port> productions from RFC3986

And from this, I argue different origins are admitted and should be space separated.

Solution 15

I struggled to set this up for a domain running HTTPS, so I figured I would share the solution. I used the following directive in my httpd.conf file:

    <FilesMatch "\.(ttf|otf|eot|woff)$">
            SetEnvIf Origin "^http(s)?://(.+\.)?example\.com$" AccessControlAllowOrigin=$0
            Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
    </FilesMatch>

Change example.com to your domain name. Add this inside <VirtualHost x.x.x.x:xx> in your httpd.conf file. Notice that if your VirtualHost has a port suffix (e.g. :80) then this directive will not apply to HTTPS, so you will need to also go to /etc/apache2/sites-available/default-ssl and add the same directive in that file, inside of the <VirtualHost _default_:443> section.

Once the config files are updated, you will need to run the following commands in the terminal:

a2enmod headers
sudo service apache2 reload

Solution 16

PHP Code:

$httpOrigin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : null;
if (in_array($httpOrigin, [
    'http://localhost:9000', // Co-worker dev-server
    'http://127.0.0.1:9001', // My dev-server
])) header("Access-Control-Allow-Origin: ${httpOrigin}");
header('Access-Control-Allow-Credentials: true');

Solution 17

If you are having trouble with fonts, use:

<FilesMatch "\.(ttf|ttc|otf|eot|woff)$">
    <IfModule mod_headers>
        Header set Access-Control-Allow-Origin "*"
    </IfModule>
</FilesMatch>

Solution 18

HTTP_ORIGIN is not used by all browsers. How secure is HTTP_ORIGIN? For me it comes up empty in FF.
I have the sites that I allow access to my site send over a site ID, I then check my DB for the record with that id and get the SITE_URL column value (www.yoursite.com).

header('Access-Control-Allow-Origin: http://'.$row['SITE_URL']);

Even if the send over a valid site ID the request needs to be from the domain listed in my DB associated with that site ID.

Solution 19

Here's an expanded option for apache that includes some of the latest and planned font definitions:

<FilesMatch "\.(ttf|otf|eot|woff|woff2|sfnt|svg)$">
    <IfModule mod_headers.c>
        SetEnvIf Origin "^http(s)?://(.+\.)?(domainname1|domainname2|domainname3)\.(?:com|net|org)$" AccessControlAllowOrigin=$0$1$2
        Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
        Header set Access-Control-Allow-Credentials true
    </IfModule>
</FilesMatch>

Solution 20

For a fairly easy copy / paste for .NET applications, I wrote this to enable CORS from within a global.asax file. This code follows the advice given in the currently accepted answer, reflecting whatever origin back is given in the request into the response. This effectively achieves '*' without using it.

The reason for this is that it enables multiple other CORS features, including the ability to send an AJAX XMLHttpRequest with the 'withCredentials' attribute set to 'true'.

void Application_BeginRequest(object sender, EventArgs e)
{
    if (Request.HttpMethod == "OPTIONS")
    {
        Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
        Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
        Response.AddHeader("Access-Control-Max-Age", "1728000");
        Response.End();
    }
    else
    {
        Response.AddHeader("Access-Control-Allow-Credentials", "true");

        if (Request.Headers["Origin"] != null)
            Response.AddHeader("Access-Control-Allow-Origin" , Request.Headers["Origin"]);
        else
            Response.AddHeader("Access-Control-Allow-Origin" , "*");
    }
}

Solution 21

To facilitate multiple domain access for an ASMX service, I created this function in the global.asax file:

protected void Application_BeginRequest(object sender, EventArgs e)
{
    string CORSServices = "/account.asmx|/account2.asmx";
    if (CORSServices.IndexOf(HttpContext.Current.Request.Url.AbsolutePath) > -1)
    {
        string allowedDomains = "http://xxx.yyy.example|http://aaa.bbb.example";

        if(allowedDomains.IndexOf(HttpContext.Current.Request.Headers["Origin"]) > -1)
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", HttpContext.Current.Request.Headers["Origin"]);

        if(HttpContext.Current.Request.HttpMethod == "OPTIONS")
            HttpContext.Current.Response.End();
    }
}

This allows for CORS handling of OPTIONS verb also.

Solution 22

PHP code example for matching subdomains.

if( preg_match("/http:\/\/(.*?)\.yourdomain.example/", $_SERVER['HTTP_ORIGIN'], $matches )) {
        $theMatch = $matches[0];
        header('Access-Control-Allow-Origin: ' . $theMatch);
}

Solution 23

AWS Lambda/API Gateway

For information on how to configure multiple origins on Serverless AWS Lambda and API Gateway - albeit a rather large solution for something one would feel should be quite straightforward - see here:

https://stackoverflow.com/a/41708323/1624933


It is currently not possible to configure multiple origins in API Gateway, see here: https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors-console.html), but the recommendation (in the answer above) is:

  • inspect the Origin header sent by the browser
  • check it against a whitelist of origins
  • if it matches, return the incoming Origin as the Access-Control-Allow-Origin header, else return a placeholder (default origin).

The simple solution is obviously enabling ALL (*) like so:

exports.handler = async (event) => {
    const response = {
        statusCode: 200,
        headers: {
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS
        },
        body: JSON.stringify([{

But it might be better to do this on the API Gateway side (see 2nd link above).

Solution 24

I have https://stackoverflow.com/a/7454204/13779574 this code worked well but gives an error when the user enters that page. I fixed this problem with this code.

if (isset($_SERVER['HTTP_ORIGIN'])) {
   $http_origin = $_SERVER['HTTP_ORIGIN'];
   if ($http_origin == "http://localhost:3000" || $http_origin == "http://api.loc/"){  
      header("Access-Control-Allow-Origin: $http_origin");
   }
}

Solution 25

And one more answer in Django. To have a single view allow CORS from multiple domains, here is my code:

def my_view(request):
    if 'HTTP_ORIGIN' in request.META.keys() and request.META['HTTP_ORIGIN'] in ['http://allowed-unsecure-domain.com', 'https://allowed-secure-domain.com', ...]:
        response = my_view_response() # Create your desired response data: JsonResponse, HttpResponse...
        # Then add CORS headers for access from delivery
        response["Access-Control-Allow-Origin"] = request.META['HTTP_ORIGIN']
        response["Access-Control-Allow-Methods"] = "GET" # "GET, POST, PUT, DELETE, OPTIONS, HEAD"
        response["Access-Control-Max-Age"] = "1000"  
        response["Access-Control-Allow-Headers"] = "*"  
        return response

Solution 26

Google's support answer on serving ads over SSL and the grammar in the RFC itself would seem to indicate that you can space delimit the URLs. Not sure how well-supported this is in different browsers.

Solution 27

If you try so many code examples like me to make it work using CORS, it is worth to mention that you have to clear your cache first to try if it actually works, similiar to issues like when old images are still present, even if it's deleted on the server (because it is still saved in your cache).

For example CTRL + SHIFT + DEL in Google Chrome to delete your cache.

This helped me using this code after trying many pure .htaccess solutions and this seemed the only one working (at least for me):

    Header add Access-Control-Allow-Origin "http://google.com"
    Header add Access-Control-Allow-Headers "authorization, origin, user-token, x-requested-with, content-type"
    Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"

    <FilesMatch "\.(ttf|otf|eot|woff)$">
        <IfModule mod_headers.c>
            SetEnvIf Origin "http(s)?://(www\.)?(google.com|staging.google.com|development.google.com|otherdomain.com|dev02.otherdomain.net)$" AccessControlAllowOrigin=$0
            Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
        </IfModule>
    </FilesMatch>

Also note that it is widely spread that many solutions say you have to type Header set ... but it is Header add .... Hope this helps someone having the same troubles for some hours now like me.

Solution 28

Below answer is specific to C#, but the concept should be applicable to all the different platforms.

To allow Cross Origin Requests from a web api, You need to allow Option requests to your Application and Add below annotation at controller level.

[EnableCors(UrlString,Header, Method)] Now the origins can be passed only a s string. SO if you want to pass more than one URL in the request pass it as a comma seperated value.

UrlString = "https://a.hello.com,https://b.hello.com"

Solution 29

Only a single origin can be specified for the Access-Control-Allow-Origin header. But you can set the origin in your response according to the request. Also don't forget to set the Vary header. In PHP I would do the following:

    /**
     * Enable CORS for the passed origins.
     * Adds the Access-Control-Allow-Origin header to the response with the origin that matched the one in the request.
     * @param array $origins
     * @return string|null returns the matched origin or null
     */
    function allowOrigins($origins)
    {
        $val = $_SERVER['HTTP_ORIGIN'] ?? null;
        if (in_array($val, $origins, true)) {
            header('Access-Control-Allow-Origin: '.$val);
            header('Vary: Origin');

            return $val;
        }

        return null;
    }

  if (allowOrigins(['http://localhost', 'https://localhost'])) {
      echo your response here, e.g. token
  }

Solution 30

i was also facing same problem. my client was on 9097,api gateway on 9098,microservice on .... Actually i was using spring cloud Api gateway
in my gateway yml file i had allowed crossorigin like-- ... allowedOrigins: "http://localhost:9097"

also in my microservice i was using @crossOrigin

when client sent request to api gateway, two "Access-Control-Allow-Origin" headers was coming in responce [one from api yml file and one from microservice @crossorigin] so browser blocked request

i solved it as--

    @Bean
public RouteLocator getRL(RouteLocatorBuilder builder) {
    
return  builder.routes()
    
        .route(p-> 
         "/friendlist","/guest/**"
                )
         .filters(f ->{
             //f.removeResponseHeader("Access-Control-Allow-Origin");
             //f.addResponseHeader("Access-Control-Allow-Origin","http://localhost:9097");
             f.setResponseHeader("Access-Control-Allow-Origin","http://localhost:9097");
             return f;
         })
        .uri("lb://OLD-SERVICE")
        
        
    ).build();      
}