AMP

CORS en AMP

Muchos de los componentes y extensiones de AMP aprovechan los endpoints remotos mediante el uso de solicitudes para el Intercambio de recursos de origen cruzado (CORS). En este documento se explican los aspectos más importantes sobre el uso de CORS en AMP. Para obtener más información sobre CORS, consulte la especificación W3 de CORS.

¿Por qué necesito CORS para mi propio origen?

Posiblemente esté confundido sobre por qué necesitaría CORS para administrar las solicitudes que llegan a su propio origen, a continuación, profundizaremos en eso.

Los componentes de AMP que obtienen datos dinámicos (por ejemplo, amp-form, amp-list, etc.) llevan a cabo solicitudes CORS en endpoints remotos para recuperar los datos. Si su página AMP incluye dichos componentes, deberá habilitar CORS para que esas solicitudes no fallen.

Representemos esto con un ejemplo:

Supongamos que tiene una página AMP en la que se registran productos con precios. Para actualizar los precios en la página, el usuario hace clic en un botón que recupera los precios más recientes desde un endpoint JSON (lo realiza a través del componente amp-list). El JSON se encuentra en su dominio.

Bien, entonces si la página se encuentra en mi dominio y JSON también está en mi dominio. ¡No veo ningún problema!

Vaya, pero ¿cómo llegó el usuario a su página AMP? ¿accede desde una página que está almacenada en el caché? Es muy probable que su usuario no haya accedido a su página AMP directamente, sino que haya descubierto su página mediante otra plataforma. Por ejemplo, Google Search utiliza el caché AMP de Google para renderizar rápidamente las páginas AMP. Estas son páginas almacenadas en el caché y proporcionan sus servicios desde el caché AMP de Google, que es un dominio diferente. Cuando su usuario hace clic en el botón para actualizar los precios en su página, la página que está almacenada en el caché de AMP envía una solicitud a su dominio de origen para obtener los precios, lo cual genera una discrepancia entre los orígenes (caché -> dominio de origen). Para permitir tales solicitudes de origen cruzado es necesario que habilite CORS, de lo contrario las solicitudes fallarán.

CORS and Cache

Muy bien, ¿qué debo hacer ahora?

  1. Para las Páginas AMP que obtienen datos dinámicos, asegúrese de probar la versión en caché de esas páginas, no se restrinja a probarlas solo en su propio dominio. (Consulte la sección Cómo probar CORS en AMP que se encuentra más adelante).
  2. Siga las instrucciones que se encuentran en este documento para habilitar las solicitudes y respuestas de CORS.

Cómo utilizar las cookies en las solicitudes CORS

En la mayoría de los componentes de AMP que utilizan las solicitudes CORS se configura automáticamente el modo de credenciales o permiten que el autor lo habilite de forma opcional. Por ejemplo, el componente amp-list obtiene contenido dinámico desde un endpoint JSON mediante CORS y permite que autor establezca el modo de credenciales mediante el atributo credentials.

Ejemplo: Cómo incorporar contenido personalizado en un componente amp-list utilizando cookies

<amp-list
credentials="include"
src="<%host%>/json/product.json?clientId=CLIENT_ID(myCookieId)"

>   <template type="amp-mustache">

    Your personal offer: ${{price}}
  </template>
</amp-list>

En cuanto especifique el modo credenciales, el origen puede incluir cookies en la solicitud CORS y también establecer cookies para la respuesta (sujeto a las restricciones de las cookies de terceros).

Restricciones de las cookies de terceros

Las mismas restricciones de las cookies de terceros que se especifican en el navegador también se aplican a las solicitudes CORS acreditadas en AMP. Estas restricciones dependen del navegador y de la plataforma, pero para algunos navegadores, solo pueden establecerse cookies si el usuario visitó previamente la primera parte de una ventana (la parte superior) del origen. O dicho de otra manera, solo después de que el usuario haya visitado directamente el sitio web de origen. Teniendo en cuenta esto, si accede a un servicio a través de CORS no puede suponer que podrán establecerse las cookies de forma predeterminada.

La seguridad de CORS en AMP

Con el fin de verificar que las solicitudes y respuestas sean válidas y seguras para sus páginas AMP, debe:

  1. Verificar la solicitud.
  2. Enviar la respuesta apropiada a los encabezados.

Si está usando Node en su backend, puede usar el middleware AMP mediante CORS, que es parte del conjunto de herramientas de AMP.

Cómo verificar las solicitudes CORS

Cuando su endpoint reciba una solicitud CORS, realice lo siguiente:

  1. Compruebe que el encabezado Origin de CORS provenga de un origen permitido (el origen del editor + los cachés de AMP).
  2. Si no hay un encabezado Origin, verifique que la solicitud provenga del mismo origen (mediante AMP-Same-Origin).

1) Cómo autorizar las solicitudes CORS de orígenes específicos

Los endpoints de CORS reciben la solicitud del origen a través del encabezado HTTP Origin. Los endpoints solo deben permitir solicitudes que provengan de: (1) el propio origen del editor y de (2) cada cacheDomain con un origen registrado en https://cdn.ampproject.org/caches.json.

Por ejemplo, los endpoints deben permitir solicitudes que provengan de:

  • Un subdominio del caché AMP de Google: https://<publisher's domain>.cdn.ampproject.org
    (por ejemplo, https://nytimes-com.cdn.ampproject.org)

Para obtener más información sobre los formatos de las URL que se utilizan para el caché de AMP, consulte los siguientes recursos:

2) Cómo autorizar las solicitudes que provengan del mismo origen

Para las solicitudes que provengan del mismo origen dónde falte el encabezado Origin, AMP establece el siguiente encabezado personalizado:

AMP-Same-Origin: true

El tiempo de ejecución de AMP envía este encabezado personalizado cuando se realiza una solicitud XHR desde el mismo origen (es decir, un documento que presta servicios desde una URL sin caché). Permite solicitudes que contengan el encabezado AMP-Same-Origin:true.

Cómo enviar la respuesta a los encabezados CORS

Después de verificar la solicitud CORS, la respuesta que se origine de HTTP debe contener los siguientes encabezados:

El encabezado de respuesta Access-Control-Allow-Origin:

Este encabezado es un requisito de la especificación W3 de CORS donde origin se refiere al origen de la solicitud que se autorizó mediante el encabezado de la solicitud CORS Origin (por ejemplo, "https://.cdn.ampproject.org").

A pesar de que la especificación W3 de CORS permite devolver el valor de * en la respuesta, para mejorar la seguridad, debe hacer lo siguiente:

  • Si el encabezado Origin está presente, compruebe y repita la validación del encabezado Origin.

Cómo procesar las solicitudes de cambio de estado

Realice estos controles de validación antes de procesar la solicitud. Esta validación le proporciona protección contra los ataques CSRF y evita el procesamiento de solicitudes que provengan de fuentes no confiables.

Antes de procesar solicitudes que podrían cambiar el estado de su sistema (por ejemplo, cuando un usuario se suscribe o cancela su suscripción de una lista de correos), verifique lo siguiente:

Si contiene el encabezado Origin:

  1. Si el origen no coincide con alguno de los siguientes valores, detenga el proceso y devuelva una respuesta de error:
  • <publisher's domain>.cdn.ampproject.org
  • el origen del editor (alias “el suyo”)

donde * corresponde a un comodín y no es un asterisco real (*).

  1. De lo contrario, procese la solicitud.

Si NO contiene el encabezado Origin:

  1. Verifique que la solicitud contenga el encabezado AMP-Same-Origin: true. Si la solicitud no contiene este encabezado, detenga el proceso y devuelva una respuesta de error.
  2. De lo contrario, procese la solicitud.

Ejemplo paso a paso: cómo administrar solicitudes y respuestas CORS

Debe tener en cuenta dos escenarios para las solicitudes CORS que recibe su endpoint:

  1. Una solicitud que provenga del mismo origen.
  2. Una solicitud proveniente un origen que se almacene en el caché (desde un caché de AMP).

Analicemos estos escenarios con un ejemplo. En nuestro ejemplo, administramos el sitio example.com que aloja una página AMP llamada article-amp.html. La página AMP contiene un amp-list para recuperar datos dinámicos de un archivo data.json que también está alojado en example.com. Si deseamos procesar las solicitudes en nuestro archivo data.json que provengan de nuestra página AMP. Estas solicitudes podrían tener el mismo origen que la página AMP (la cual no está almacenada en el caché) o provenir de la página AMP (que está almacenada en el caché) pero tiene un origen diferente.

Los orígenes que están permitidos

Basándonos en lo que sabemos sobre CORS y AMP (desde la sección Verificar las solicitudes CORS que se encuentra más arriba), en nuestro ejemplo aceptaremos las solicitudes que provengan de los siguientes dominios:

  • example.com --- Provenientes del dominio del editor
  • example-com.cdn.ampproject.org --- Provenientes del subdominio para el caché AMP de Google

Los encabezados de respuesta para las solicitudes que están permitidas

En las solicitudes que provengan de orígenes permitidos, nuestra respuesta deberá incluir los siguientes encabezados:

Access-Control-Allow-Origin: <origin>

Estos son encabezados de respuesta adicionales que podríamos incluir en nuestra respuesta CORS:

Access-Control-Allow-Credentials: true
Content-Type: application/json
Access-Control-Max-Age: <delta-seconds>
Cache-Control: private, no-cache

La pseudológica de CORS

Nuestra lógica para administrar las solicitudes y respuestas de CORS puede simplificarse en el siguiente pseudocódigo:

IF CORS header present
IF origin IN allowed-origins
allow request & send response
ELSE
deny request
ELSE
IF "AMP-Same-Origin: true"
allow request & send response
ELSE
deny request

Ejemplo del código que se utiliza en CORS

Este es el ejemplo de una función en JavaScript que podríamos utilizar para habilitar las solicitudes y respuestas de CORS:

function assertCors(req, res, opt_validMethods, opt_exposeHeaders) {
var unauthorized = 'Unauthorized Request';
var origin;
var allowedOrigins = [
'https://example.com',
'https://example-com.cdn.ampproject.org',
'https://cdn.ampproject.org',
];
var allowedSourceOrigin = 'https://example.com'; //publisher's origin
// If same origin
if (req.headers['amp-same-origin'] == 'true') {
origin = sourceOrigin;
// If allowed CORS origin & allowed source origin
} else if (
allowedOrigins.indexOf(req.headers.origin) != -1 &&
sourceOrigin == allowedSourceOrigin
) {
origin = req.headers.origin;
} else {
res.statusCode = 403;
res.end(JSON.stringify({message: unauthorized}));
throw unauthorized;
}

res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Origin', origin);
}

Nota: Si desea un ejemplo funcional del código, consulte amp-cors.js.

Escenario 1: cómo recibir solicitudes desde la página de AMP que provengan del mismo origen

En el siguiente escenario, la página article-amp.html solicita el archivo data.json, cuyos orígenes son los mismos.

Si analizamos esta solicitud, encontraremos:

Request URL: https://example.com/data.json
Request Method: GET
AMP-Same-Origin: true

Como esta solicitud proviene del mismo origen, no hay un encabezado Origin, pero el encabezado personalizado de la solicitud AMP de AMP-Same-Origin: true está presente. Permitiremos esta solicitud ya que proviene del mismo origen (https://example.com).

Nuestros encabezados de respuesta serían los siguientes:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://example.com

Escenario 2: cómo recibir solicitudes desde una página que se almacenó en el caché de AMP

En el siguiente escenario, la página article-amp.html que se almacenó en el caché AMP de Google solicita el archivo data.json, en este caso los orígenes son diferentes.

Si analizamos esta solicitud, encontraremos:

Request URL: https://example.com/data.json
Request Method: GET
Origin: https://example-com.cdn.ampproject.org

Como esta solicitud contiene un encabezado Origin, verificaremos que provenga de un origen permitido. Debido a que esta solicitud proviene de un origen permitido, entonces podemos autorizarla.

Nuestros encabezados de respuesta serían los siguientes:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://example-com.cdn.ampproject.org

Cómo trabajar con fuentes que se almacenaron en el caché

En el caché AMP de Google se almacenan documentos, imágenes y fuentes AMP HTML con la finalidad de optimizar la velocidad de la página de AMP. Conforme esto hace que la página AMP sea más rápida, también deseamos que proteja los recursos que se almacenen en el caché. Para ello, realizaremos un cambio en la forma que el caché de AMP responde ante los recursos que se almacenaron en el caché, aunque generalmente para las fuentes se respeta el valor del origen Access-Control-Allow-Origin.

Comportamiento anterior (antes de octubre del 2019)

Cuando una página de AMP estaba cargando https://example.com/some/font.ttf desde el atributo @font-face src, el caché de AMP almacena temporalmente en el caché la fuente del archivo y funcionará como un recurso sin la necesidad de tener el comodín Access-Control-Allow-Origin, como se muestra a continuación:

  • URL https://example-com.cdn.ampproject.org/r/s/example.com/some/font.tff
  • Access-Control-Allow-Origin: *

Comportamiento nuevo (a partir de octubre del 2019)

Mientras la implementación actual sea laxa podría conducir a un uso imprevisto de las fuentes que provienen de sitios de origen cruzado. En este cambio, el caché de AMP comenzará a responder exactamente con el mismo valor de Access-Control-Allow-Origin que el servidor de origen. Para cargar correctamente las fuentes desde el documento que está almacenado en el caché de AMP, deberá aceptar el origen del caché de AMP mediante el encabezado.

Un ejemplo de la implementación sería el siguiente:

function assertFontCors(req, res, opt_validMethods, opt_exposeHeaders) {
var unauthorized = 'Unauthorized Request';
var allowedOrigins = [
'https://example.com',
'https://example-com.cdn.ampproject.org',
];
// If allowed CORS origin
if (allowedOrigins.indexOf(req.headers.origin) != -1) {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
} else {
res.statusCode = 403;
res.end(JSON.stringify({message: unauthorized}));
throw unauthorized;
}
}

Por ejemplo, si desea cargar /some/font.ttf en https://example.com/amp.html, el servidor de origen debe responder con el encabezado Access-Control-Allow-Origin, como se muestra a continuación.

Si la fuente de su archivo es correcta para ser accesible desde cualquier origen, puede responder con un comodín Access-Control-Allow-Origin, el caché de AMP también repetirá la validación, lo cual significa que responderá con Access-Control-Allow-Origin: *. Si ya tiene esta configuración, no es necesario cambiar nada.

Planeamos realizar este cambio a mediados de octubre del 2019 y esperamos que todos los editores de AMP que utilizan fuentes de alojamiento propio verifiquen si esto les afecta.

Plan de implementación

  • 30-09-2019: la versión del lanzamiento contiene un control más preciso sobre cuáles son los dominios en los que se aplica este cambio. Esa compilación debería implementarse en el transcurso de esta semana.
  • 07-10-2019: los dominios de prueba se habilitarán para las pruebas manuales.
  • 14-10-2019: (pero dependiendo de cuáles hayan sido los resultados de las pruebas): la función se implementará de manera general.

Siga todas las novedades sobre este tema aquí.

Cómo probar CORS en AMP

Cuando esté probando sus páginas de AMP, asegúrese de incluir pruebas de las versiones que se hayan almacenado en el caché.

Cómo verificar la página mediante la URL que se almacenó en el caché

Para garantizar que la página almacenada en el caché de AMP se renderice y funcione correctamente, siga los siguientes pasos:

  1. Desde su navegador, abra la URL que el caché de AMP utilizaría para acceder a su página AMP. Puede establecer el formato que tendrá la URL en el caché con la herramienta AMP By Example.

Por ejemplo:

  • URL: https://amp.dev/documentation/guides-and-tutorials/start/create/
  • Formato de URL para el caché de AMP: https://www-ampproject-org.cdn.ampproject.org/c/s/www.ampproject.org/docs/tutorials/create.html
  1. Abra las herramientas de desarrollo para su navegador, verifique que no haya errores y que todos los recursos se hayan cargado correctamente.

Cómo verificar los encabezados de respuesta en su servidor

Puede utilizar el comando curl para verificar que su servidor envíe los encabezados de respuesta HTTP correctos. En el comando curl, proporcione la solicitud de la URL y los encabezados personalizados que desee agregar.

Sintaxis: curl <request-url> -H <custom-header> - I

Cómo probar la solicitud desde el mismo origen

En una solicitud con el mismo origen, el sistema de AMP agrega el encabezado personalizado AMP-Same-Origin:true.

Aquí encontrará nuestro comando curl para probar una solicitud que provenga de https://ampbyexample.com en el archivo examples.json (en el mismo dominio):

curl 'https://amp.dev/static/samples/json/examples.json' -H 'AMP-Same-Origin: true' -I

En los resultados del comando se muestran los encabezados de respuesta correctos (nota: se omitió la información adicional):

HTTP/2 200
access-control-allow-headers: Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token
access-control-allow-credentials: true
access-control-allow-origin: https://ampbyexample.com
access-control-allow-methods: POST, GET, OPTIONS

Cómo probar la solicitud de la página AMP almacenada en el caché

En una solicitud CORS que no proviene del mismo dominio (es decir, del caché), el encabezado origin es parte de la solicitud.

Aquí encontrará nuestro comando curl para probar una solicitud que proviene desde la página AMP, la cual está almacenada en el caché AMP de Google en el archivo examples.json:

curl 'https://amp.dev/static/samples/json/examples.json' -H 'origin: https://ampbyexample-com.cdn.ampproject.org' -I

En los resultados del comando se muestran los encabezados de respuesta correctos:

HTTP/2 200
access-control-allow-headers: Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token
access-control-allow-credentials: true
access-control-allow-origin: https://ampbyexample-com.cdn.ampproject.org
access-control-allow-methods: POST, GET, OPTIONS