You've built a fully-functional API with Apigility and hosted it on your domain, e.g., https://mygreatapp.example.com/api.
Now let's assume you developed a widget in JavaScript that fetches data from your API, and you would like to use it in different websites, located on different domains.
Every single request will fail. Why? How can you fix this?
Making a request from one domain to a different domain is protected by the same-origin policy (RFC 6454).
The policy is quite simple: this browser compares the combination of protocol/schema, host, and port from both the client and the server. If there's an exact match, it passes. If a single element differs, it fails.
To relax those restrictions, you need to implement Cross-Origin Resource Sharing (aka CORS). This standard adds two headers:
Origin
for requestsAccess-Control-Allow-Origin
(ACAO) for responses
Every modern browser supports these headers, and emits the Origin
header for any cross-origin
requests.
The website http://www.sexywidgets.com hosts your widget. When it makes a request in JavaScript to
your API, the Origin
header contains this website's fully qualified domain name (FQDN):
GET /api/foo HTTP/1.1
Origin: http://www.sexywidgets.com
If you want this request to succeed, your API needs to send back an Access-Control-Allow-Origin
header in its response, as follows:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://www.sexywidgets.com
While you can potentially manage this on your own, a module already exists that allows setting and configuring the various CORS headers: ZfrCors.
To install ZfrCors, run the following composer command:
$ composer require "zfr/zfr-cors:1.*"
Alternately, manually add the following to your composer.json
, in the require
section:
"require": {
"zfr/zfr-cors": "1.*"
}
And then run composer update
to ensure the module is installed. (Don't remove any existing entries
under the require
section!)
Finally, add the module name to your project's config/application.config.php
under the modules
key:
return [
/* ... */
'modules' => [
/* ... */
'ZfrCors',
],
/* ... */
];
First copy the file vendor/zfr/zfr-cors/config/zfr_cors.global.php.dist
to
config/autoload/zfr-cors.global.php
. *(Note the removal of the .dist
extension.)
To carry on with our example, let's adapt the ZfrCors module configuration file as follows:
return [
'zfr_cors' => [
/**
* Set the list of allowed origins domain with protocol.
*/
'allowed_origins' => ['http://www.sexywidgets.com'],
/**
* Set the list of HTTP verbs.
*/
'allowed_methods' => ['GET', 'OPTIONS'],
/**
* Set the list of headers. This is returned in the preflight request to indicate
* which HTTP headers can be used when making the actual request
*/
'allowed_headers' => ['Authorization', 'Content-Type'],
/**
* Set the max age of the preflight request in seconds. A non-zero max age means
* that the preflight will be cached during this amount of time
*/
// 'max_age' => 120,
/**
* Set the list of exposed headers. This is a whitelist that authorize the browser
* to access to some headers using the getResponseHeader() JavaScript method. Please
* note that this feature is buggy and some browsers do not implement it correctly
*/
// 'exposed_headers' => [],
/**
* Standard CORS requests do not send or set any cookies by default. For this to work,
* the client must set the XMLHttpRequest's "withCredentials" property to "true". For
* this to work, you must set this option to true so that the server can serve
* the proper response header.
*/
// 'allowed_credentials' => false,
],
];
allowed_methods
: we just allowed theGET
andOPTIONS
verbs.GET
only permits "READ" operations.OPTIONS
should always be kept in this array, as it's mandatory for certain browsers. If you want to allow other operations, make sure they are listed here.allowed_headers
: we addedAuthorization
for OAuth2 requests, for example. You may also want to addAccept
andContent-Type
.See the ZfrCors GitHub page for further information and configuration.