Skip to main content

Let a browser app call the API

The orders team builds a web application served from https://app.example.com. Its pages call the orders API on the gateway, which is another origin, so the browser asks the gateway first whether that is allowed. cors gives the browser that permission. This guide puts cors on orders-api for that application, with the user's cookies.

A browser sends the origin of the page that makes a call in the Origin header. Before a call that goes beyond a plain GET, a POST with a JSON body for example, it sends a preflight: an OPTIONS request that names the method in Access-Control-Request-Method. The page reads an answer only when the answer names its origin in Access-Control-Allow-Origin. At the end, the gateway answers the application's requests and preflights with the headers the browser expects, and the browser lets the application send its cookies.

Before you start​

  • This guide starts from orders-api as Route requests to a backend leaves it, with key-auth from Require an API key. A plugin from another guide that this page does not name can change the answers below; remove it first or work on a copy of the route.
  • The host name of your gateway. In the console it is the first part of the Address line on a route's Overview tab, api.example.com in this page's examples; replace it in every request below.
  • The requests below call /orders/42.
  • From the team that builds the application: its origin, scheme included, the methods its pages use and the request headers they send. This guide uses https://app.example.com, the methods GET,POST,PUT,DELETE and the headers Authorization,Content-Type.
  • Whether the application sends the user's cookies or an Authorization header. If it does, the browser needs the gateway's permission to send them, and the origins, methods and headers must then be named one by one.
  • The Editor or Environment administrator role in this environment.

Steps​

1. Let the route take the preflight​

A route answers 404 to a method it does not take, before any plugin runs. orders-api takes GET and POST only, so the browser's OPTIONS preflight would never reach cors.

Open orders-api under Routes and select Edit route. Under Matching, check OPTIONS beside GET and POST in Methods. Select Save route.

2. Choose cors​

Select the route's Plugins tab, then Add plugin at the top right of the tab. Open the Security category, or type cors in the search box. Select cors, then Configure. A panel with the plugin's form opens.

3. Name the origin, the methods and the headers​

Under Basics, fill in every field. With credentials on, a field left at its default is refused when you save.

FieldValueWhy
Allow originshttps://app.example.comThe origin of the application, with its scheme. Separate several with commas.
Allow credentialonLets the browser send the user's cookies and an Authorization header with each call.
Allow methodsGET,POST,PUT,DELETEThe methods the browser may ask for. The route still decides what it answers.
Allow headersAuthorization,Content-TypeThe request headers the application's pages may send.

The list in Allow methods only tells the browser what it may ask for. orders-api takes GET, POST and now OPTIONS, so it answers a PUT or a DELETE with 404 whatever the list says.

4. Set what the application may read and add the plugin​

Under Behavior, fill in two more fields and leave Allow origins by regex empty.

FieldValueWhy
Expose headersX-Request-IdA response header the application's script may read. Without it, the script reads only the headers every browser exposes.
Preflight cache time3600The seconds a browser may keep a preflight answer before it asks again.

Select Add plugin at the bottom of the panel. The tab now lists cors, and the plugin is saved like this:

The Add cors panel with the web app's origin, the methods and headers it may use, and credentials allowed.
cors · as the console saves it
{
"allow_origins": "https://app.example.com",
"allow_methods": "GET,POST,PUT,DELETE",
"allow_headers": "Authorization,Content-Type",
"allow_credential": true,
"expose_headers": "X-Request-Id",
"max_age": 3600
}

Check it​

Send the call the application's page makes, a GET with the application's origin. With key-auth on orders-api, add the key of mobile-app in the x-api-key header, as in Require an API key.

curl
curl -i -L 'https://<gateway host>/orders/42' \
-H 'Origin: https://app.example.com'
Allowed origin
A request from the page at https://app.example.com is answered 200 with Access-Control-Allow-Origin: https://app.example.com, so the browser lets the page read the answer.
Cookies allowed
The same answer carries Access-Control-Allow-Credentials: true, so the browser sends the cookies of the API with the request and accepts the ones it sets.
Headers the page may read
The same answer carries Access-Control-Expose-Headers: X-Request-Id, so the page may read that header; every other header of the answer stays hidden from it.

Then send the preflight the browser sends before a POST.

curl
curl -i -L -X OPTIONS 'https://<gateway host>/orders/42' \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: POST'
Allowed methods
The preflight the browser sends before a POST, an OPTIONS request naming the method it wants, is answered 200 by the gateway with Access-Control-Allow-Methods: GET,POST,PUT,DELETE.
Allowed headers
The same preflight answer carries Access-Control-Allow-Headers: Authorization,Content-Type, the request headers the page may send.
How long it holds
The same preflight answer carries Access-Control-Max-Age: 3600, so the browser keeps the preflight answer for an hour and asks again only after that.

Keep the origins once for many routes​

When several routes serve the same sites, keep the list of their origins once, as a named set in the environment. Each route's cors then names the set instead of the origins.

In the menu, select Plugins and open cors. In the Environment-wide settings card (plugin metadata in the API), under Allow origins, add a row for the set. Type its name, storefront, on the left and its origins on the right, comma separated, https://shop.example.com,https://www.example.com. Select Save settings. The sets are shared by every use of cors in the environment.

The example below holds two sets, storefront and partners. The steps above create storefront only.

cors environment-wide settings · as the console saves it
{
"allow_origins": {
"storefront": "https://shop.example.com,https://www.example.com",
"partners": "https://portal.partner.example.com"
}
}

Then add cors to a route those sites call, as in steps 2 to 4, with one setting only. Under Advanced, in Allow origins by metadata, type a value and press Enter for each, here storefront. Select Add plugin at the bottom of the panel.

To try this without losing the setup for https://app.example.com, work on a copy of orders-api whose cors names only the set. A request from either origin of the set is answered with its own origin.

curl
curl -i -L 'https://<gateway host>/orders/42' \
-H 'Origin: https://shop.example.com'
From the first origin of the set
On a route whose cors plugin names the set storefront, a request from https://shop.example.com is answered 200 with Access-Control-Allow-Origin: https://shop.example.com.
From the second
A request from https://www.example.com, the other origin of the same set, is answered 200 with Access-Control-Allow-Origin: https://www.example.com.

What to know​

  • Allow credential on by itself is refused when you save. Allow origins, Allow methods and Allow headers read * until you name them, and a * beside credentials is refused.
  • With credentials on, a * in Expose headers is refused as well. Name the headers one by one.
  • An origin is written with its scheme, https://app.example.com and not app.example.com. An entry without its scheme is refused when you save.
  • Two stars, **, in Allow origins let every origin in even with credentials. Any site on the internet can then call the API with the user's cookies, so name your own sites' origins instead.
  • Browsers cap Preflight cache time at a ceiling of their own.
  • cors does not decide which clients may call the API. A client outside a browser ignores these headers, and only a lock such as key-auth from Require an API key keeps it out.
G8 dev