CORS in REST channels
Preflights, allowed origins and response headers - serving browser applications from any origin.
Browsers enforce the same-origin policy, so a web application served from one origin can call your REST channels on another origin only if the response includes CORS headers. This page explains what Zato handles automatically and how to serve browser applications from any origin.
What is built in
Every REST channel handles CORS automatically for a fixed set of origins:
https://zato.iohttp://localhostandhttp://127.0.0.1, on any port
For these origins, a preflight OPTIONS request is answered before authentication runs, with status 204 No Content and these headers:
Access-Control-Allow-Origin: <the origin echoed back>
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: X-API-Key, Authorization, Content-Type
Access-Control-Max-Age: 86400
On the actual request that follows, Access-Control-Allow-Origin is added to the response. The origin is always echoed back individually, never *.
This means local development against a Zato environment works out of the box - a frontend running on http://localhost:3000 can call your channels with no CORS configuration anywhere.
Serve your own origins
For requests from any other origin, the built-in handling does not add headers and your service sets them itself, through self.response.headers:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class GetCustomer(Service):
name = 'demo.rest.get-customer'
def handle(self) -> 'None':
# The origin your frontend is served from
allowed_origin = 'https://app.example.com'
self.response.headers['Access-Control-Allow-Origin'] = allowed_origin
self.response.payload = {'customer_id': 123, 'name': 'Alexis Martin'}
If the browser sends a preflight first - which it does for anything beyond simple GET and POST requests, and always when custom headers such as X-API-Key are involved - the OPTIONS request reaches the channel like any other request. Handle it with a handle_OPTIONS method:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class GetCustomer(Service):
name = 'demo.rest.get-customer'
def _add_cors_headers(self) -> 'None':
self.response.headers['Access-Control-Allow-Origin'] = 'https://app.example.com'
self.response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
self.response.headers['Access-Control-Allow-Headers'] = 'X-API-Key, Content-Type'
self.response.headers['Access-Control-Max-Age'] = '86400'
def handle_OPTIONS(self) -> 'None':
self._add_cors_headers()
def handle_GET(self) -> 'None':
self._add_cors_headers()
self.response.payload = {'customer_id': 123, 'name': 'Alexis Martin'}
Preflights sent by browsers never include credentials, so a channel whose service answers its own preflights cannot require security on the OPTIONS method. Assign no security definition to such a channel and authenticate the actual requests in the service, or use two channels, one without security for OPTIONS and one with security for the remaining methods, both pointing to the same URL path.
See also
| Page | What it covers |
|---|---|
| REST channels | Creating the channels that browser applications call |
| HTTP verbs | The handle_OPTIONS and other verb-specific handlers |
| Authentication | The security definitions that preflight requests interact with |