Validando una petición CORS de AMP con Symfony

Integra los formularios de AMP con Symfony

Gerardo Fernández
Nov 29, 2018 · 4 min read

Recientemente he estado desarrollando una web para una startup dedicada al sector de la organización de eventos para empresa y dado que las secciones apenas tenían complicación me animé a hacerla completamente en AMP con el fin de seguir familiarizándome con esta librería.

Además, decidí usar Symfony ya que varias secciones dependen de contenido creado por medio de un CMS y gracias a que la versión 4 es bastante ligera no le vi mayor problema en meter de nuevo mi framework favorito de PHP.

Dicho esto, el desarrollo fue bastante stándard (Symfony + Webpack Encore + Sonata Admin) hasta llegar a la parte del formulario de contacto, el cual podéis ver aquí:

Básicamente, la complicación a la hora de crear un formulario con AMP es que estas páginas una vez que son indexadas por Google, no se sirven desde nuestro dominio, sino que son enviadas desde un CDN de Google donde éste almacena nuestras páginas, generalmente alguno de estos 3 (notad como convierten el dominio):

- https://eventarte-es.cdn.ampproject.org

- https://eventarte.es.amp.cloudflare.com

- https://cdn.ampproject.org

Dado que en AMP el submit de los formularios se realiza mediante una llamada AJAX, os podéis imaginar el problema: CORS.

Por tanto, es necesario añadir un poco de código en la parte del servidor de cara a que el formulario pueda ser enviado correctamente desde los CDN de Google. Vamos a ello!

Creando el formulario

Y su template es algo parecido a ésto:

Como veis, añado al formulario el atributo action-xhr especificando la URL absoluta contra la que se enviarán los datos. Hasta aquí, nada nuevo que no aparezca bien en la documentación propia de AMP Project.

Validando la petición

https://eventarte-es.cdn.ampproject.org/contacto

Cuando rellene el formulario, la URL contra la que se se enviarán los datos será:

[POST] https://eventarte.es/contacto

Y si no lo hemos configurado correctamente, dará un fallo de CORS. Veamos cómo solucionarlo.

Para que resulte más sencillo explicarlo, os dejo el código de un servicio de Symfony que se encarga de generar una Response adecuada en el caso de que la validación de los CORS sea correcta. A continuación, explicaré los puntos más importantes:

Lo primero de todo, el constructor recibe el servicio RequestStack con el fin de poder acceder a la request en curso (igual valdría también pasársela como argumento al método) y los siguientes parámetros:

  • allowedOrigins , es decir, un array en yml con todos los dominios aceptados para enviar peticiones CORS. En nuestro caso:
allowedOrigins:  - https://domain.es  - https://domain-es.cdn.ampproject.org  - https://domain-es.amp.cloudflare.com  - https://cdn.ampproject.org
  • allowedSourceOrigin , es decir, el dominio base, en nuestro ejemplo:
allowedSourceOrigin: https://domain.es

Dicho esto, el método validateRequest primero comprueba si nos fue enviado el parámetro __amp_source_origin , parámetro que contiene el dominio asociado al CDN desde el que está navegando el usuario, en nuestro caso el valor de __amp_source_origin debería ser https://domain.es .

Y comienzan las comprobacaciones.

  • Primero comprueba si la petición llevaba la cabecera amp-same-origin , la cual sirve para indicar que la petición se está enviando desde el mismo dominio y no desde un CDN (de ahí que su valor sea TRUE o FALSE ), por lo que la petición es válida.
  • Si no, se comprueba que el contenido de la cabecera ORIGIN sea un dominio incluido dentro de nuestro array allowedOrigins y que el valor del parámetro GET __amp_source_origin sea el de nuestro dominio ( allowedSourceOrigin , es decir, https://domain.es). Si es así, la petición es válida.

En el caso de que no se cumplan ninguna de las dos condiciones anteriores devolveremos un código de error indicando que la validación CORS no tuvo éxito (en mi caso estoy devolviendo un HTTP_FORBIDDEN .

Si la validación fue correcta, devolveremos las 4 cabeceras indicadas en el código teniendo en cuenta que:

  • Access-Control-Allow-Origin llevará el dominio desde el cual se envió la petición
  • Y AMP-Access-Control-Allow-Source-Origin llevará nuestro dominio original.

Procesando el formulario

public function contact(Request $request,EntityManagerInterface $em,AmpService $ampService) {  $contact = new Contact();  $form = $this->createForm(ContactFormType::class, $contact);  $form->handleRequest($request);  if ($form->isSubmitted() && $form->isValid()) {    $response = $ampService->validateRequest();    if ($response->getStatusCode() == Response::HTTP_FORBIDDEN) {      return $response;   }   .....

Es decir, emplearemos el servicio AMPService para validar los CORS de la llamada y poder recibir formularios correctamente desde nuestras páginas AMP.

Espero que este artículo os sirva para acortar trabajo si tenéis que implementar alguna vez un formulario sobre AMP y si queréis darle una vuelta a la web sobre la que he preparado este artículo, he tenido la suerte de ver algunos de los eventos que organizan y la verdad es que trabajan muy muy bien.

¿Quieres recibir más artículos como este?

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store