How to handle CORS for AMP

Viewed 4547

I have a page that uses amp-list to dynamically list products using a JSON file. I have my directory structure as follows: *root*/amp/product-name/ and then within the product-name directory I have an index.html with the AMP HTML and a products.json which contains the product data that I display on page. Everything is working as expected if I visit the page directly: https://example.com/amp/product-name/

The problem comes in when I visit from the ampproject.org CDN.

Here's the actual page, if you visit the console you can see there is a CORS error. How can I handle this? Documentation is not clear on how to handle exactly, just a very vague overview, so for someone new this doesn't make sense to me and I can't just intuit how to do it. I know I need to have a request handler of some sort, which I've partially made by copying the code from AMP's own app.js they link to in their documentation. Within the amp-list element I previously had the src pointed directly to the JSON file, but now I'm seeing that I need to point to a handler (like a JS file) and then have the handler set the request header and then output the proper JSON.

Here's what I have for the request handler:

/**
 * @param {string} url
 * @param {string} param
 * @param {*} value
 * @return {string}
 */
function addQueryParam(url, param, value) {
  const paramValue = encodeURIComponent(param) + '=' + encodeURIComponent(value);
  if (!url.includes('?')) {
    url += '?' + paramValue;
  } else {
    url += '&' + paramValue;
  }
  return url;
}

function enableCors(req, res, origin, opt_exposeHeaders) {
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Access-Control-Allow-Origin', 'https://www-perfectimprints-com.cdn.ampproject.org');
  res.setHeader('Access-Control-Expose-Headers', ['AMP-Access-Control-Allow-Source-Origin'].concat(opt_exposeHeaders || []).join(', '));
  if (req.query.__amp_source_origin) {
    res.setHeader('AMP-Access-Control-Allow-Source-Origin', req.query.__amp_source_origin);
  }
}

function assertCors(req, res, opt_validMethods, opt_exposeHeaders, opt_ignoreMissingSourceOrigin) {
  // Allow disable CORS check (iframe fixtures have origin 'about:srcdoc').
  if (req.query.cors == '0') {
    return;
  }
  const validMethods = opt_validMethods || ['GET', 'POST', 'OPTIONS'];
  const invalidMethod = req.method + ' method is not allowed. Use POST.';
  const invalidOrigin = 'Origin header is invalid.';
  const invalidSourceOrigin = '__amp_source_origin parameter is invalid.';
  const unauthorized = 'Unauthorized Request';
  let origin;
  if (validMethods.indexOf(req.method) == -1) {
    res.statusCode = 405;
    res.end(JSON.stringify({
      message: invalidMethod
    }));
    throw invalidMethod;
  }
  if (req.headers.origin) {
    origin = req.headers.origin;
    if (!ORIGIN_REGEX.test(req.headers.origin)) {
      res.statusCode = 500;
      res.end(JSON.stringify({
        message: invalidOrigin
      }));
      throw invalidOrigin;
    }
    if (!opt_ignoreMissingSourceOrigin && !SOURCE_ORIGIN_REGEX.test(req.query.__amp_source_origin)) {
      res.statusCode = 500;
      res.end(JSON.stringify({
        message: invalidSourceOrigin
      }));
      throw invalidSourceOrigin;
    }
  } else if (req.headers['amp-same-origin'] == 'true') {
    origin = getUrlPrefix(req);
  } else {
    res.statusCode = 401;
    res.end(JSON.stringify({
      message: unauthorized
    }));
    throw unauthorized;
  }
  enableCors(req, res, origin, opt_exposeHeaders);
}

And here's what I'm using for the HTML.

<amp-list credentials="include" width="auto" height="1500" layout="fixed-height" src="https://perfectimprints.com/amp/handle-amp-requests.js" class="m1">
      <template type="amp-mustache" id="amp-template-id">
        <div class="product" style="padding-top: 1em;">
          <a class="wrapper-link" href="{{ link }}">
            <amp-img alt="{{ title }}" width="1000" height="1000" src="{{ src }}" layout="responsive"></amp-img>
            <h3 class="product-name text-centered">
              {{title}}
            </h3>
            <h4 class="text-centered sku margin-minus-1">{{ sku }}</h4>
            <div class="text-centered get-price">
              <a class="text-centered get-price-link" href="{{ link }}">Get pricing now</a>
            </div>
          </a>
        </div>
      </template>
      <div overflow role="button" aria-label="Show more">
        Show more
       </div>
    </amp-list>

https://www-perfectimprints-com.cdn.ampproject.org/c/s/www.perfectimprints.com/amp/trick-or-treat-bags/index.html

3 Answers

This was the solution that worked for me.

Header set Access-Control-Allow-Credentials "true"
Header set Access-Control-Allow-Origin "https://example-com.cdn.ampproject.org"
Header set Access-Control-Allow-Source-Origin "https://example.com"
Header set Access-Control-Expose-Headers AMP-Access-Control-Allow-Source-Origin
Header set AMP-Access-Control-Allow-Source-Origin "https://example.com"

Thank you to @Craig Scott for the prompt to help me solve.

Woe be the poor, inexperienced soul who deals with AMP pages.

Presuming you're on an Apache server, you could set your CORS header in your htaccess file like this:

Header set Access-Control-Allow-Origin "https://www-perfectimprints-com.cdn.ampproject.org"
Header set Access-Control-Allow-Origin "https://www-perfectimprints-com.amp.cloudflare.com"

Then ensure your htaccess file is uploaded to the directory that contains your website HTML. That's how it's set-up on the site I manage and everything works for us.

For people using nodejs, here's the syntax:

        res.set('AMP-Redirect-To', encodedUrl);
        res.set('Access-Control-Allow-Origin', websiteHostname);
        res.set('AMP-Access-Control-Allow-Source-Origin', websiteHostname);
        res.set('Access-Control-Expose-Headers', [
            'AMP-Access-Control-Allow-Source-Origin',
            'AMP-Redirect-To',
        ]);
        res.set('Access-Control-Allow-Credentials', 'true');

ِAnother solution is to use the amp-toolbox-cors library and set it's verifyOrigin option to false.

import ampCors from 'amp-toolbox-cors';

app.use(
    ampCors({
        verifyOrigin: false,
    }),
);

beware that all origins are allowed like that. In reality you should allow just two.

Hope it saves somebody's time in the future!

Related