Calls from other sites (CORS)
Apps/Ship/Calls from other sites (CORS)

Calls from other sites (CORS)

A page on another site that calls your app's routes with fetch needs the app to allow its origin (CORS). Name the origins in settings.ib.ts, and Inference Bureau answers for the app: preflights, the headers on every response, and a line in Logs when a browser is refused.

When you need it

The callerCORS
The app's own pages, embedded admin pages includedNot needed: same origin.
Theme blocks and storefront scripts through the app proxyNot needed: the proxy is on the store's own domain.
Customer account, checkout and admin UI extensions calling IB_EXTENSION_URLHandled: that address allows any origin, since the session token is the credential.
Sidekick tools calling the app's routesHandled: https://extensions.shopifycdn.com is always allowed.
A headless storefront, another of your sites, a widget embedded elsewhereName the origin in cors.origins.
A Shop Mini calling the app as its backendName http://localhost:*: Minis call from localhost on a port of their choosing, in development and in the Shop app alike.

Allowing an origin

cors in settings.ib.ts takes effect within seconds of saving in development, and with a deploy in production:

import type { AppSettings } from "@inferencebureau/apps-sdk";

export const settings: AppSettings = {
  type: "ib/settings/v1",
  platform: "shopify",
  cors: {
    origins: ["https://shop.example.com", "https://*.example.com", "http://localhost:3000"],
    paths: ["/api/*"],
    exposeHeaders: ["x-total-count"],
  },
};
FieldWhat it does
originshttps://host (with a port if it has one), https://*.host for any of its subdomains (not the host itself), http://localhost:<port> for local work, http://localhost:* for any local port, or * for any site. No paths.
pathsWhich of the app's paths it covers: exact, or ending in * for everything under it. Every path unless named.
credentialstrue lets pages send cookies with credentials: "include". Not with *, which browsers refuse. Off by default: send a token in Authorization instead.
exposeHeadersResponse headers the calling page may read besides the basic ones (content-type and the like).

An entry that doesn't check out is left out and named in Problems; the rest still apply.

CORS decides which pages may read the app's answers in a browser. It isn't access control: anything outside a browser can call the route anyway. Routes still check who's calling.

What Inference Bureau answers

  • Preflights from an allowed origin to a covered path are answered before they reach the app, with the method and headers the page asked for, so routes don't need an OPTIONS handler and a sleeping development server isn't woken just to answer one.
  • Every response to an allowed origin gets Access-Control-Allow-Origin and Vary: Origin, including the page shown while a development server wakes, and the ones shown when the app can't be reached. The calling page sees a 503 it can retry, not a CORS error.
  • A route that sets its own Access-Control-Allow-Origin keeps it; nothing is added on top.
  • Calls from origins or to paths cors doesn't cover go to the app as they are, so a route can still handle CORS itself.

Refused calls in Logs

A browser shows a refused call only as a CORS error, without a status or a reason. When a page's preflight gets an answer that doesn't allow it, Logs gets a failed app event named cors <origin>, saying whether settings.ib.ts leaves out the origin or the path. Each origin is logged at most once an hour per environment.