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-apias 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.comin 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 methodsGET,POST,PUT,DELETEand the headersAuthorization,Content-Type. - Whether the application sends the user's cookies or an
Authorizationheader. 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.
| Field | Value | Why |
|---|---|---|
| Allow origins | https://app.example.com | The origin of the application, with its scheme. Separate several with commas. |
| Allow credential | on | Lets the browser send the user's cookies and an Authorization header with each call. |
| Allow methods | GET,POST,PUT,DELETE | The methods the browser may ask for. The route still decides what it answers. |
| Allow headers | Authorization,Content-Type | The 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.
| Field | Value | Why |
|---|---|---|
| Expose headers | X-Request-Id | A response header the application's script may read. Without it, the script reads only the headers every browser exposes. |
| Preflight cache time | 3600 | The 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:
{
"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 -i -L 'https://<gateway host>/orders/42' \
-H 'Origin: https://app.example.com'
- Allowed origin
- A request from the page at
https://app.example.comis answered200withAccess-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 -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 answered200by the gateway withAccess-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.
{
"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 -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.comis answered200withAccess-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 answered200withAccess-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.comand 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.
Related
- Require an API key: who may call the route at all.
- Sign people in with your identity provider: the session cookie a browser application may carry.
- cors, Route and Environment-wide settings in the reference.