ChatGPT解决这个技术问题 Extra ChatGPT

Access-Control-Allow-Origin Multiple Origin Domains?

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?

Using the most recent Firefox, neither comma seperated, nor space seperated domains did work. Matching against a list of domains and putting a single host in the headers is still better security and does work properly.
If you're struggling with this for HTTPS, I found a solution.
important note: allowing only cretain domains in the Access-Control-Allow-Origin header does not mean that other domains cannot trigger a method on this endpoint (e.g. REST API method). It just means that disallowed origins cannot use the result in javascript (browser ensures this). For restricting access to an endpoint for specific domains use a server-side request filter that e.g. returns HTTP 401 for disallowed domains.
You should always append Vary: Origin header when you want to use multiple URLs, see: fetch.spec.whatwg.org/#cors-protocol-and-http-caches

k
kmgdev

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>

How would you add wildcard subdomains like: *.example.com or wildcard ports like: localhost:*
For anyone wondering you can do (.+\.google.com) instead of (google.com|staging.google.com)
How would this behave if there is no match? Which would be the output of Access-Control-Allow-Origin?
That regex is not well designed; in particular, insecure origins (using the http scheme) shouldn't be allowed, and DNS label separators should be escaped (\. instead of .); otherwise, an attacker could for instance buy the developmentzgoogle.com domain and mount cross-origin attacks from there.
N
Nikolay Ivanov

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");
}

HTTP_ORIGIN is not reliable, see stackoverflow.com/questions/41231116/…
P
Patrick Mevzek

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.


t
the Tin Man

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.


Handy to have an example for multiple domains: ^(https?:\/\/localhost:\d+)$|^(https?:\/\/.+\.yourdomain\.com)$ Here's it in action... regex101.com/r/GZHTLB/1 It's crazy gobbledegook but that regex101 site helps decipher it all.
m
mjallday

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;
    }
}

e
eyecatchUp

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();
});

P
Patrick Mevzek

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.


Should probably check that $request_headers['Origin']; exists, otherwise any direct requests are going to trigger an E_NOTICE.
M
Mark

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.


A
Adriano Rosa

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.


P
Patrick Mevzek

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>

Great. Helped me.
h
hernvnc

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\.com(:[0-9]+)?)) {
  set $cors "true";
}
if ($http_origin ~* (https?://.*\.my-other-domain\.com(:[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';
}

any . in the domain needs to be escaped, otherwise it matches any character. Also, you can have just one if statement instead of using a variable
C
Community

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


M
Michael

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;
    }
}

P
Patrick Mevzek

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.


A
Alex W

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

I like this option and combined/modified it with the implementation that @George has. Sometimes servers don't have a2enmod available, so all you have to do is check your main httpd.conf to see if the line: LoadModule headers_module modules/mod_headers.so is uncommented.
My origin had a port number, so I modified the regular expression to include that: ^http(s)?://(.+\.)?example\.com(:\d+)?$
L
Liakos

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');

DO not use this in production, its not safe.
n
noun

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>

C
Community

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.


M
Mike Kormendy

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>

Q
QA Collective

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" , "*");
    }
}

P
Patrick Mevzek

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.


P
Patrick Mevzek

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);
}

t
timhc22

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


Access-Control-Allow-Credentials: true is not allowed with wildcard Access-Control-Allow-Origin: *. Set a specific <origin> instead.
@Tom, yea, not sure why that was in there, I can't remember, but I might have copied it from the defaults that were added on AWS? Thanks for pointing that out though.
A
Akbarali

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");
   }
}

DO not use this in production, its not safe.
S
Silvain

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

C
Community

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.


'serving ads over ssl' links to the spec w3.org/TR/cors/#access-control-allow-origin-response-header which adds a note, "In practice the origin-list-or-null production is more constrained. Rather than allowing a space-separated list of origins, it is either a single origin or the string "null".
While it's important to note that detail, when a specification says "In practice", it doesn't mean that it's only valid to do it that way. It means that if you do it that way, you may run into problems because the majority of implementors either implement the spec incorrectly or incompletely. The specification does allow for a space-separated list of origins, which you can see here in the EBNF under origin-list: tools.ietf.org/html/rfc6454#section-7.1
A
AlexioVay

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.


s
sakshi agrawal

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"


S
Simon

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
  }

a
akhil Raj Kunwar

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();      
}