Cross-Origin Resource Sharing (CORS)

Il Cross-Origin Resource Sharing (CORS) è un meccanismo che usa header HTTP addizionali per indicare a un browser che un'applicazione Web in esecuzione su un'origine (dominio) dispone dell'autorizzazione per accedere alle risorse selezionate da un server di origine diversa. Un'applicazione web invia una cross-origin HTTP request quando richiede una risorsa che ha un'origine (protocollo, dominio e porta) differente dalla propria.

Esempio di cross-origin request: Il codice Javascript di  frontend per un'applicazione web servita da http://domain-a.com utilizza XMLHttpRequest per inviare una richiesta a http://api.domain-b.com/data.json.

Per ragioni di sicurezza, i browser limitano le cross-origin HTTP requests che vengono generate all'interno degli scripts. Ad esempio, XMLHttpRequest e la Fetch API seguono la same-origin policy. Ciò significa che un'applicazione web che utilizza queste API può solamente richiedere risorse HTTP dalla stessa origine di caricamento dell'applicazione, a meno che la risposta dall'altra origine includa i corretti header CORS.

Il meccanismo CORS supporta richieste e trasferimenti dati sicuri fra browsers e web servers. I browser moderni usano CORS in container API come XMLHttpRequest o Fetch per aiutare a mitigare i rischi di richieste HTTP cross-origin.

Chi dovrebbe leggere questo articolo?

Tutti, davvero.

Più specificamente, questo articolo è per amministratori web, sviluppatori server side e front end. I browser moderni gestiscono i componenti client della cross-origin sharing, inclusi gli headers e applicazione delle policy. Ma questo nuovo standard richiede che i server gestiscano nuovi headers di richiesta e risposta. Un altro articolo per sviluppator server side che discute la cross-origin sharing da una prospettiva server (con esempi di codice PHP ) è una lettura supplementare.

Quali tipi di richieste usano CORS?

Questo cross-origin sharing standard è usato per abilitare richieste HTTP cross-site per:

Questo articolo è una discussione generale sul Cross-Origin Resource Sharing e include una trattazione degli header HTTP necessari.

Panoramica funzionale

Lo standard Cross-Origin Resource Sharing funziona aggiungendo nuovi header HTTP che consentono ai server di descrivere l'insieme di origini che sono autorizzate a leggere quelle informazioni tramite un web browser. In aggiunta, per i metodi di richiesta HTTP che possono causare effetti collaterali sui dati del server (in particolare, per i metodi HTTP diversi da GET, o per l'utilizzo di POST con determinati MIME types), la specifica prevede che il browser "anticipi" la richiesta (questa operazione è detta "preflight"), richiedendo al server i metodi supportati tramite una richiesta HTTP OPTIONS, e poi, dopo una "approvazione" del server, invii la richiesta effettiva con il metodo HTTP effettivo. I server possono anche informare i client se delle "credenziali" (inclusi Cookies e dati di autenticazione HTTP) debbano essere inviate insieme alle richieste.

Gli insuccessi CORS generano degli errori, ma per ragioni di sicurezza, i dettagli riguardo a cosa sia andato male non sono disponibili al codice JavaScript. Tutto ciò che il codice può sapere è che si è verificato un errore. L'unico modo per determinare cosa sia andato male nello specifico è guardare la console del browser per i dettagli.

Le sezioni successive discutono alcuni scenari, e provvedono un'analisi dettagliata degli header HTTP usati.

Esempi di scenari di controllo accessi

Qui presentiamo tre scenari che illustrano come funziona la Cross-Origin Resource Sharing. Tutti questi esempi utilizzano l'oggetto XMLHttpRequest, che può essere usato per creare chiamate cross-site in qualsiasi browser che le supporti.

Gli spezzoni di codice JavaScript inclusi in queste sezioni (e istanze attive di codice server che gestiscono correttamente queste richieste cross-site) possono essere viste in azione su http://arunranga.com/examples/access-control/, e funzioneranno nei browser che supportano XMLHttpRequest cross-site.

Una trattazione della Cross-Origin Resource Sharing da una prospettiva server (inclusi spezzoni di codice PHP) si possono trovare nell'articolo Server-Side Access Control (CORS).

Richieste semplici

Alcune richieste non scatenano una CORS preflight. Queste sono chiamate “richieste semplici” in questo articolo, anche se la specifica Fetch (che definisce CORS) non utilizza quel termine. Una richiesta che non scatena una CORS preflight—una cosiddetta “richiesta semplice”—è una richiesta che soddisfa tutte le seguenti condizioni:

Nota:  Queste sono lo stesso tipo di richieste cross-site che il web content può già rilasciare, e nessuna informazione è rilasciata al richiedente a meno che il server mandi un header appropriato. Quindi, siti che impediscono la falsificazione di cross-site request non hanno nulla di nuovo da temere dall controllo di accesso HTTP.
Nota: WebKit Nightly e Safari Technology Preview pongono restrizioni addizionali sui valori ammessi negli headers Accept, Accept-Language, e Content-Language. Se anche solo uno di questi headers hanno valori non-standard, per WebKit/Safari la richiesta non rispetta le condizioni di una "richiesta semplice." Quello che WebKit/Safari considera valori “non-standard” per questi headers non è documentato eccetto nei seguenti bug di Webkit: Require preflight for non-standard CORS-safelisted request headers Accept, Accept-Language, and Content-Language, Allow commas in Accept, Accept-Language, and Content-Language request headers for simple CORS, e Switch to a blacklist model for restricted Accept headers in simple CORS requests. Nessun altro browser implementa queste restrizioni addizionali, perché queste restrizioni non fanno parte della specifica.

Per esempio, supponiamo che una pagina web su dominio http://foo.example tenti di accedere a contenuto su dominio http://bar.other. In Javascript, verrebbe scritto un codice simile al seguente in foo.example:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/public-data/';
   
function callOtherDomain() {
  if(invocation) {    
    invocation.open('GET', url, true);
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}

Tutto ciò porterà ad un semplice scambio di informazioni tra client e server, usando headers CORS per manipolare i privilegi:

Vediamo cosa il browser manderà al server in questo caso, e come risponderà il server:

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61 
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[Dati XML]

Le linee 1 - 10 sono gli header mandati. L'header più importante qui è Origin alla linea 10, che mostra che l'invocazione origina dal dominio http://foo.example.

Le linee 13 - 22 mostrano la risposta HTTP dal server al dominio http://bar.other. In risposta, il server manda un header Access-Control-Allow-Origin mostrato nella linea 16. L'uso dell'header Origin e Access-Control-Allow-Origin dimostrano il protocollo di controllo accesso nella sua forma più semplice. In questo caso, il server risponde con Access-Control-Allow-Origin: * il che significa che la risorsa può essere acceduta da qualsiasi dominio. Se i proprietary della risorsa su http://bar.other vogliono restringere accesso alla risorsa alle sole richieste provenienti da http://foo.example, risponderebbero con:

Access-Control-Allow-Origin: http://foo.example

Nota che ora, nessun dominio a parte http://foo.example (identificato dall'header ORIGIN: nella richiesta, come nella linea 10) può accedere alla risorsa in maniera cross-site. L'header Access-Control-Allow-Origin deve contenere il valore che è stato mandato nell'header Origin della richiesta.

Richieste in preflight

Al contrario delle "richieste semplici" discusse sopra, le richieste "in preflight" (anticipate) mandano prima una richiesta HTTP tramite il metodo OPTIONS alla risorsa nell'altro dominio, per determinare se la richiesta vera e propria è sicura. Richieste cross-site vengono anticipate in questo modo perché potrebbero avere implicazioni per la sicurezza dei dati utenti.

In particolare, una richiesta è anticipate se anche solo una delle seguenti condizioni è vera:

Note: WebKit Nightly e Safari Technology Preview pongono ulteriori restrizioni sul valori ammessi negli header Accept, Accept-Language, e Content-Language. Se anche solo uno di questi headers ha un valore non-standard, WebKit/Safari effettua la richiesta in preflight. Quello che WebKit/Safari considerano valori “non-standard” non è documentato eccetto nei seguenti bug di WebKit: Require preflight for non-standard CORS-safelisted request headers Accept, Accept-Language, and Content-Language, Allow commas in Accept, Accept-Language, and Content-Language request headers for simple CORS, e Switch to a blacklist model for restricted Accept headers in simple CORS requests. Nessun altro browser implementa questa restrizioni aggiuntive, perché non sono parte della specifica.

Il seguente è un esempio di una richiesta che verrà effettuata in preflight.

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';
    
function callOtherDomain(){
  if(invocation)
    {
      invocation.open('POST', url, true);
      invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
      invocation.setRequestHeader('Content-Type', 'application/xml');
      invocation.onreadystatechange = handler;
      invocation.send(body); 
    }
}

......

Nell'esempio sopra, la linea 3 crea un corpo XML che viene mandato con una richiesta POST alla linea 8. Nella linea 9 viene specificato un header "non-standard" (X-PINGOTHER: pingpong). Questi headers non fanno parte del protocollo HTTP/1.1, ma sono utili per applicazioni web. La richiesta è eseguita in preflight perché usa un Content-Type di application/xml e la richiesta usa un header non-standard.

(Nota: come descritto sopra, la richiesta POST non include gli header Access-Control-Request-*; questi sono necessari solo per le richieste OPTIONS.)

Diamo un'occhiata allo scambio complete tra client e server. Il primo scambio è la richiesta e risposta in preflight:

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

Quando la richiesta in preflight è completa, la richiesta vera e propria viene mandata:

POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache

<?xml version="1.0"?><person><name>Arun</name></person>


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Payload compresso con GZIP]

Le linee 1 - 12 sopra rappresentano le richieste in preflight con il metodo OPTIONS. Il browser determina che deve mandare questo in base ai parametri della prima richiesta. OPTIONS è un metodo HTTP/1.1 usato per ricevere informazioni aggiuntive dal server ed è un metodo "safe" (non può cambiare la risorsa). Oltre alla richiesta OPTIONS vengono mandate altre due richieste (linee 10 e 11):

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

L'header Access-Control-Request-Method notifica il server che la richiesta vera e propria verrà mandata con un metodo POST. L'header Access-Control-Request-Headers dice al server che verrà mandata con gli header personalizzati X-PINGOTHER e Content-Type. Ora il server può determinare se vuole accettare una richiesta in queste circostanze.

Le linee 14-26 sono la risposta e indicano che il metodo richiesta (POST) e gli headers (X-PINGOTHER) sono accettabili. In particolare, vediamo le linee 17-20:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

Il server risponde con Access-Control-Allow-Methods e dice che POST e GET possono essere usati per accedere alla risorsa. Questo header è simile a Allow ma è usato solo nel contesto del controllo d'accesso.

The server also sends Access-Control-Allow-Headers with a value of "X-PINGOTHER, Content-Type", confirming that these are permitted headers to be used with the actual request. Like Access-Control-Allow-Methods, Access-Control-Allow-Headers is a comma separated list of acceptable headers.

Finally, Access-Control-Max-Age gives the value in seconds for how long the response to the preflight request can be cached for without sending another preflight request. In this case, 86400 seconds is 24 hours. Note that each browser has a maximum internal value that takes precedence when the Access-Control-Max-Age is greater.

Preflighted requests and redirects

Not all browsers currently support following redirects after a preflighted request. If a redirect occurs after a preflighted request, some browsers currently will report an error message such as the following.

The request was redirected to 'https://example.com/foo', which is disallowed for cross-origin requests that require preflight

Request requires preflight, which is disallowed to follow cross-origin redirect

The CORS protocol originally required that behavior but was subsequently changed to no longer require it. However, not all browsers have implemented the change, and so still exhibit the behavior that was originally required.

So until all browsers catch up with the spec, you may be able to work around this limitation by doing one or both of the following:

  • change the server-side behavior to avoid the preflight and/or to avoid the redirect—if you have control over the server the request is being made to
  • change the request such that it is a simple request that doesn’t cause a preflight

But if it’s not possible to make those changes, then another way that may be possible is to this:

  1. Make a simple request (using Response.url for the Fetch API, or XMLHttpRequest.responseURL) to determine what URL the real preflighted request would end up at.
  2. Make another request (the “real” request) using the URL you obtained from Response.url or XMLHttpRequest.responseURL in the first step.

However, if the request is one that triggers a preflight due to the presence of the Authorization header in the request, you won’t be able to work around the limitation using the steps above. And you won’t be able to work around it at all unless you have control over the server the request is being made to.

Requests with credentials

The most interesting capability exposed by both XMLHttpRequest or Fetch and CORS is the ability to make "credentialed" requests that are aware of HTTP cookies and HTTP Authentication information. By default, in cross-site XMLHttpRequest" or Fetch invocations, browsers will not send credentials. A specific flag has to be set on the XMLHttpRequest" object or the Request constructor when it is invoked.

In this example, content originally loaded from http://foo.example makes a simple GET request to a resource on http://bar.other which sets Cookies. Content on foo.example might contain JavaScript like this:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
    
function callOtherDomain(){
  if(invocation) {
    invocation.open('GET', url, true);
    invocation.withCredentials = true;
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}

Line 7 shows the flag on XMLHttpRequest that has to be set in order to make the invocation with Cookies, namely the withCredentials boolean value. By default, the invocation is made without Cookies. Since this is a simple GET request, it is not preflighted, but the browser will reject any response that does not have the Access-Control-Allow-Credentials: true header, and not make the response available to the invoking web content.

Here is a sample exchange between client and server:

GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain


[text/plain payload]

Although line 11 contains the Cookie destined for the content on http://bar.other, if bar.other did not respond with an Access-Control-Allow-Credentials: true (line 19) the response would be ignored and not made available to web content.

Credentialed requests and wildcards

When responding to a credentialed request, the server must specify an origin in the value of the Access-Control-Allow-Origin header, instead of specifying the "*" wildcard.

Because the request headers in the above example include a Cookie header, the request would fail if the value of the Access-Control-Allow-Origin header were "*". But it does not fail: Because the value of the Access-Control-Allow-Origin header is "http://foo.example" (an actual origin) rather than the "*" wildcard, the credential-cognizant content is returned to the invoking web content.

Note that the Set-Cookie response header in the example above also sets a further cookie. In case of failure, an exception—depending on the API used—is raised.

Third-party cookies

Note that cookies set in CORS responses are subject to normal third-party cookie policies. In the example above, the page is loaded from foo.example, but the cookie on line 22 is sent by bar.other, and would thus not be saved if the user has configured their browser to reject all third-party cookies.

The HTTP response headers

This section lists the HTTP response headers that servers send back for access control requests as defined by the Cross-Origin Resource Sharing specification. The previous section gives an overview of these in action.

Access-Control-Allow-Origin

A returned resource may have one Access-Control-Allow-Origin header, with the following syntax:

Access-Control-Allow-Origin: <origin> | *

Access-Control-Allow-Origin specifies either a single origin, which tells browsers to allow that origin to access the resource; or else — for requests without credentials — the "*" wildcard, to tell browsers to allow any origin to access the resource.

For example, to allow code from the origin http://mozilla.org to access the resource, you can specify:

Access-Control-Allow-Origin: http://mozilla.org

If the server specifies a single origin rather than the "*" wildcard, then the server should also include Origin in the Vary response header — to indicate to clients that server responses will differ based on the value of the Origin request header.

Access-Control-Expose-Headers

The Access-Control-Expose-Headers header lets a server whitelist headers that browsers are allowed to access. For example:

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

This allows the X-My-Custom-Header and X-Another-Custom-Header headers to be exposed to the browser.

Access-Control-Max-Age

The Access-Control-Max-Age header indicates how long the results of a preflight request can be cached. For an example of a preflight request, see the above examples.

Access-Control-Max-Age: <delta-seconds>

The delta-seconds parameter indicates the number of seconds the results can be cached.

Access-Control-Allow-Credentials

The Access-Control-Allow-Credentials header Indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. Note that simple GET requests are not preflighted, and so if a request is made for a resource with credentials, if this header is not returned with the resource, the response is ignored by the browser and not returned to web content.

Access-Control-Allow-Credentials: true

Credentialed requests are discussed above.

Access-Control-Allow-Methods

The Access-Control-Allow-Methods header specifies the method or methods allowed when accessing the resource. This is used in response to a preflight request. The conditions under which a request is preflighted are discussed above.

Access-Control-Allow-Methods: <method>[, <method>]*

An example of a preflight request is given above, including an example which sends this header to the browser.

Access-Control-Allow-Headers

The Access-Control-Allow-Headers header is used in response to a preflight request to indicate which HTTP headers can be used when making the actual request.

Access-Control-Allow-Headers: <field-name>[, <field-name>]*

The HTTP request headers

This section lists headers that clients may use when issuing HTTP requests in order to make use of the cross-origin sharing feature. Note that these headers are set for you when making invocations to servers. Developers using cross-site XMLHttpRequest capability do not have to set any cross-origin sharing request headers programmatically.

Origin

The Origin header indicates the origin of the cross-site access request or preflight request.

Origin: <origin>

The origin is a URI indicating the server from which the request initiated. It does not include any path information, but only the server name.

Note: The origin can be the empty string; this is useful, for example, if the source is a data URL.

Note that in any access control request, the Origin header is always sent.

Access-Control-Request-Method

The Access-Control-Request-Method is used when issuing a preflight request to let the server know what HTTP method will be used when the actual request is made.

Access-Control-Request-Method: <method>

Examples of this usage can be found above.

Access-Control-Request-Headers

The Access-Control-Request-Headers header is used when issuing a preflight request to let the server know what HTTP headers will be used when the actual request is made.

Access-Control-Request-Headers: <field-name>[, <field-name>]*

Examples of this usage can be found above.

Specifications

Specification Status Comment
Fetch
The definition of 'CORS' in that specification.
Living Standard New definition; supplants W3C CORS specification.

Browser compatibility

Update compatibility data on GitHub
DesktopMobile
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung Internet
Access-Control-Allow-OriginChrome Full support 4Edge Full support 12Firefox Full support 3.5IE Full support 10Opera Full support 12Safari Full support 4WebView Android Full support 2Chrome Android Full support YesFirefox Android Full support 4Opera Android Full support 12Safari iOS Full support 3.2Samsung Internet Android Full support Yes

Legend

Full support  
Full support

Compatibility notes

  • Internet Explorer 8 and 9 expose CORS via the XDomainRequest object, but have a full implementation in IE 10.
  • While Firefox 3.5 introduced support for cross-site XMLHttpRequests and Web Fonts, certain requests were limited until later versions. Specifically, Firefox 7 introduced the ability for cross-site HTTP requests for WebGL Textures, and Firefox 9 added support for Images drawn on a canvas using drawImage().

See also