Definición de CORS
Antes de definir CORS se definirán algunos conceptos fundamentales para entender CORS.
origen
Dada una URL, el origen es la primera parte que está compuesta por el esquema: el host y el puerto (si está presente).
origen = esquema + host + puerto
Por ejemplo, en https://localhost:8080/encuestas
el origen es https://localhost:8080
y /encuesta
es el path
mismo-origen (same-origin)
Se refiere al caso en el que cliente hace una petición a un servidor y el servidor tiene el mismo origen que el cliente.
https://aurora.net/home -> https://aurora.net/api/pos?id=290
origen-cruzado (cross-origen)
El cliente hace una petición a un servidor cuyo origen es distinto al del cliente.
https://localhost:8080/home -> https://aurora.net/api/pos?id=290
https://localhost:8080/home -> https://127.0.0.1:8080/api/pos?id=290
https://localhost:8080/home -> https://127.0.0.1:3000/api/pos?id=290
Política de mismo-origen y CORS
Por defecto y por política de seguridad, los navegadores sólo permiten hacer peticiones HTTP al mismo origen, es decir al mismo dominio del de la página que hace la petición.
CORS (Cross-Origin Resource Sharing) es el mecanismo por el cual los navegadores permiten que una petición se haga desde un origen a otro distinto, origen-cruzado.
Para poder realizar una petición CORS el cliente no debe hacer nada, simplemente lanza la petición. El navegador hace la petición y es el servidor el que debe responder si acepta peticiones desde ese origen. Esto lo hace enviando la cabecera (header response) Access-Control-Allow-Origin
. Esta cabecera puede tener un asterisco (*
) o un origen específico. El *
significa que el servidor acepta peticiones de cualquier origen.
El navegador revisa la respuesta del navegador. Si tiene la cabecera Access-Control-Allow-Origin
y ésta tiene el valor *
o el origen coincide con el origen del cliente, entonces el navegador acepta la respuesta. En caso contrario la descarta y generalmente muestra en consola un error descriptivo de CORS.
1. navegador con origen X hace petición a origen Y
2. servidor responde
3. Si la respuesta del servidor incluye la cabecera `Access-Control-Allow-Origin` Entonces
4. Si (Access-Control-Allow-Origin == '*') o (Access-Control-Allow-Origin == X) Entonces
5. navegador acepta la respuesta
6. Si no
7. descarta respuesta y muestra error
8. Fin Si
9. Si no
10. descarta respuesta y muestra error
11. Fin Si
Access-Control-Allow-Origin
también puede tomar el valornull
para orígenes desconocidos (como por ejemplo en el caso de acceder desde un fichero en vez de un sitio web). Esto es útil mientras se está en una fase de desarrollo o pruebas, de esta manera no hace falta poner '*' que es menos restrictivo.Access-Control-Allow-Origin: null
Preflight
Arriba se ha visto de manera genérica cómo se debe tratar una petición de origen-cruzado usando CORS. En realidad, antes de hacer una petición de origen-cruzado, los navegadores que implementan CORS harán una "meta" petición previa (preflight) a la petición real.
Un preflight es una petición que hace el navegador antes de efectuar una petición CORS si dicha petición cumple uno de estos criterios:
- Usa un método HTTP distinto de
GET
,POST
, oHEAD
- Establece la cabecera
Content-Type
con un valor distinto de:- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- Establece cabeceras adicionales distintas de:
Accept
Accept-Language
Content-Language
- El objeto XMLHttpRequest contiene eventos upload
A los métodos HTTP
GET
,POST
yHEAD
se los denomina métodos simples. Los métodos simples no necesitan ser listados en la cabeceraControl-Allow-Methods
.
El preflight es una petición con el método HTTP OPTIONS
. Esta "meta" petición que hace el navegador sirve para que el servidor decida cómo responder al CORS. En el preflight el navegador envía las cabeceras:
Origin
-
Access-Control-Request-Method
(admite sólo un método) -
Access-Control-Request-Headers
(sólo si las cabeceras no son simples. Puede tener una lista de cabeceras separada por comas)
El servidor comprueba si el origen y el método HTTP están en su lista de orígenes y métodos permitidos (también comprobará las cabeceras indicadas en Access-Control-Request-Headers
, si es que lo hay). Si esta comprobación es favorable responde con un código de status en el rango 200
. La respuesta no debería llevar un body. Las cabeceras enviadas por el servidor son:
Access-Control-Allow-Origin
-
Access-Control-Allow-Methods
(en este caso puede ser una lista de comandos separados por coma) Access-Control-Allow-Headers
Ejemplo de petición preflight:
OPTIONS /api/posts HTTP/1.1
User-Agent: Chrome
Host: 127.0.0.1:8080
Accept: */*
Origin: http://localhost:3000
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Security-Level, App-Name
Ejemplo de respuesta preflight:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http//localhost:3000
Access-Control-Allow-Methods: GET, DELETE
Access-Control-Allow-Headers: Security-Level, App-Name
Ejemplo de petición:
GET /api/posts HTTP/1.1
User-Agent: Chrome
Host: 127.0.0.1:8080
Accept: */*
Origin: http://localhost:3000
Security-Level: High
App-Name: Encuesta
El algoritmo anterior, ahora modificado con los preflight quedaría así:
1. el cliente Javascript cuyo origen es X, hace una petición P al servidor con origen Y.
P contiene el método HTTP M y un conjunto de cabeceras C
2. Si M es un método HTTP simple y todas las cabeceras de C son simples Entonces
3. enviar la petición P al servidor
4. Si no
5. preparar una petición preflight F con método HTTP OPTIONS y un conjunto de cabeceras C' con:
Origin = X, Access-Control-Request-Method = M
6. Si en C hay cabeceras que no son simples Entonces
7. agregar a C' la cabecera Access-Control-Request-Headers con el listado de cabeceras no simples de C
8. Fin Si
9. enviar preflight F al servidor
10 Si servidor responde el preflight con status en el rango de 200 Entonces
11. enviar la petición P al servidor
12. Si no
13. comprobar las cabeceras de respuesta Access-Control-Allow-Origin,
Access-Control-Allow-Methods y Access-Control-Allow-Headers para determinar cuáles
son las validaciones que han fallado e informar el error
14. Fin Si
15. Fin Si
Cache. El navegador puede guardar en caché los preflights y así evitar hacer una petición extra por cada petición del cliente que requeriría preflight. La caché guarda en sus entradas origen + path.
Canvas e imágenes
En el caso de imágenes cuyo atributo src
es un origen distinto al del cliente, el navegador carga el recurso y lo muestra pero no permite hacer manipulaciones con ella como toBlob
, toDataURL
y getImageData
.
Para habilitar el CORS en el caso de imágenes, existe el atributo crossOrigin
que puede tomar los valores anonymous
o user-credentials
. user-credentials
es como el withCredentials
de XMLHttpRequest, es decir que las credenciales de usuario (por ejemplo, cookies) se envían con la petición. Si no es necesario enviar las credenciales, es preferible enviar anonymous
.
Cookies y credenciales de usuario
Por defecto el navegador no envía las credenciales de usuario como las cookies en las peticiones de origen-cruzado. El navegador tampoco expone todos las cabeceras de respuesta que envía el servidor.
Por eso CORS tiene mecanismos para remediar esas restricciones: las cabeceras de respuesta Access-Control-Allow-Credentials
y Access-Control-Expose-Headers
.
Entonces para que el uso de cookies es necesario que el cliente indique:
xhr.withCredentials = true;
document.cookie = someUniqueId;
El servidor debe responder al preflight y a la petición con las cabeceras
Access-Control-Allow-Credentials
y Access-Control-Allow-Origin
.
Cuando servidor envía la cabecera Access-Control-Allow-Credentials
con el valor true
, debe enviar la cabecera Access-Control-Allow-Origin
con un origen específico; Access-Control-Allow-Origin: *
es inválido porque aceptar cookies de cualquier origen puede generar problemas de seguridad.
Válido:
Access-Control-Allow-Origin: http://localhost:1111
Access-Control-Allow-Credentials: true
Inválido:
Access-Control-Allow-Origin: \*
Access-Control-Allow-Credentials: true
En el caso en el que el cliente tiene xhr.withCredentials = true
y el servidor responde con Access-Control-Allow-Origin: true
, el servidor puede establecer cookies en el cliente. Estas cookies serán enviadas por el navegador en las peticiones sucesivas, sin embargo, el client no podrá leer dichas cookies con Javascript.
Cabeceras de respuesta
El objeto XMLHttpRequest expone dos métodos para leer las cabeceras de respuesta: getResponseHeader
y getAllResponseHeaders
. En una petición mismo-origen estos métodos devuelven todas las cabeceras enviadas por el servidor. Cuando se trata de una petición de origen-cruzado, sólo se pueden leer las cabeceras simples.
Usando la cabecera Access-Control-Expose-Headers
el servidor puede especificar qué cabeceras puede leer el cliente.
Access-Control-Expose-Headers: X-Powered-By
Cabeceras simples:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
Resumen de cabeceras usadas en CORS
Cabeceras de petición (enviadas por el navegador cliente)
-
Origin
: Cabecera que añade el navegador. Contiene el origen del cliente en la petición. Contraparte:Access-Control-Allow-Origin
. -
Access-Control-Request-Method
: Cabecera que añade el navegador en el preflight. Contiene el método HTTP que se hará el cliente en la petición real. Contraparte:Access-Control-Allow-Methods
. -
Access-Control-Request-Headers
: Cabecera que añade el navegador en el preflight. Contiene las cabeceras HTTP que enviará el cliente en la petición real. Contraparte:Access-Control-Allow-Headers
.
Cabeceras de respuesta (enviadas por el servidor)
Access-Control-Allow-Origin
: El valor*
indica que se permiten todos los orígenes
Un valor específico indica que el servidor sólo acepta peticiones de ese origen
El valornull
indica que sólo acepta peticiones de orígenes que no son un sitio web, por ejemplo un fichero (sólo recomendado para desarrollo y pruebas). Contraparte:Origin
Access-Control-Allow-Credentials
: El valortrue
indica que el servidor soporta el uso de credenciales de usuario tales como las cookies.Access-Control-Allow-Methods
: Tiene una lista de métodos HTTP aceptados por el servidor.
No es necesario incluir los métodos simples, aunque es buena práctica hacerlo.
Esta cabecera sólo debe enviarse en respuesta a peticiones preflight. Contraparte:Access-Control-Request-Methods
Access-Control-Allow-Headers
: Tiene una lista de cabeceras HTTP aceptadas por el servidor.
Esta cabecera sólo debe enviarse en respuesta a peticiones preflight. Contraparte:Access-Control-Request-Headers
Access-Control-Max-Age
: Su valor especifica el tiempo máximo en segundos que se recomienda mantener en caché un preflight, aunque el navegador puede tener su propio valor máximo.
Esta cabecera sólo debe enviarse en respuesta a peticiones preflight.Access-Control-Expose-Headers
: Contiene las cabeceras que pueden ser leídos por el navegador. Puede tener una lista de cabeceras separada por comas.
Es opcional y no es necesario para que la petición CORS sea exitosa.
Top comments (4)
Qué ocurre si en la solicitud Preflight, en lugar de recibir un 200 OK como respuesta, se recibe algún otro código de error HTTP como 405 u otros? Acaso el navegador llevará a cabo la solicitud CORS?
El navegador espera recibir un 200 como respuesta para proseguir con la solicitud. Cualquier otro código devuelto por el servidor significa que el preflight ha fallado (por no estar permitido o incluso por un error).
Muy buen artículo. Gran trabajo. Muchas gracias
Excelente explicacion muy completa!