Multiple Values Access-Control-Allow-Origin
Secure Third Party Access to a REST API
Enforcing security policies on web applications these days is 'relatively easy' by using the correct headers in HTTP responses. Take the following example of an application:
- https://example.org delivers the frontend of the great example applications to its users. This is the domain a user knows.
- https://api.example.org is handling all information. If the user logs in, then the frontend sends a request to this domain and changes according to the response.
In this case it should not be possible for anybody else but the frontend to access the API. By now browsers try to protect the user and try to block malicious requests. Such a request would be if a user is visiting https://malicious.example.org and the web application there would try to access https://api.example.org. To prevent that, the webserver delivering the API can send the Access-Control-Allow-Origin header as follows:
Access-Control-Allow-Origin: https://example.org
This way the malicious website has no more access to our API if the user is using a recent browser.
If the application setup becomes more complex, things get much more complicated to configure. Assume that there are more systems:
- https://staging.example.org which holds the staging environment of the frontend.
- https://payment.example.org which is the external payment provider that needs to access the API to confirm payments
- https://localhost:8080 is the local server which the developer uses to try out changes locally.
There is no possibility for the Access-Control-Allow-Origin header to contain multiple domains, like separating different domains via spaces or comma. Besides specifying a single domain, only '*' is another valid option, which would allow access from everywhere. And this is no secure option in this case.
Therefore the API needs to check the origin of the request and adjust the header field accordingly. It can use the 'Origin' header on the request to check who is trying to access the resource. If it is one of the allowed domains, it sets the Access-Control-Allow-Origin accordingly. Otherwise it just sets it to https://example.org so that the request is blocked by the browser. The following example explains how a Laravel project can make use of a middleware to set this header correctly:
<?php
class CORSMiddleware
{
[...] /**
* Add Access Control headers.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
*
* @return \Illuminate\Http\Response
*/
protected function addAccessControlHeaders($request, $response)
{
$possibleOrigins = [
'https://example.com',
'https://api.example.com',
'https://staging.example.com',
'https://payment.example.org',
];
if (env('APP_ENV') == 'development') {
$possibleOrigins[] = 'https://localhost:8080';
}
if (in_array($request->header('origin'), $possibleOrigins)) {
$origin = $request->header('origin');
} else {
$origin = 'https://example.com';
}
$headers = [
'Access-Control-Allow-Origin' => $origin,
'Vary' => 'Origin',
];
foreach ($headers as $header => $value) {
$response->header($header, $value);
}
return $response;
}
}
In addition to the Access-Control-Allow-Origin header, the Vary header is set so that the browser knows that the response of this API may vary depending on the origin. Therefore it will not use any cached response when the API is called from a different frontend site in the same browser.