I also had a hard time making it work. It's not about POST vs GET, but about simple vs preflighted requests.
When you load SecureCORS
(plugin 'SecureCORS'
), it subscribes to the after_render
hook. And at the end of each request it checks if cors.origin
was specified for the current (or parent) route(s), if it's a CORS request (the Origin
header is present), and whether the Origin
header's value matches cors.origin
. If so, it adds Access-Control-Allow-Origin
, and possibly Access-Control-Allow-Credentials
, Access-Control-Expose-Headers
.
To deal with preflighted requests you need to use the cors
shortcut. It defines an OPTIONS
route for the specified URL. It takes options either from the OPTIONS
route itself, or from the target one. Again it checks if cors.origin
is provided, if the Origin
header's value matches cors.origin
, if Access-Control-Request-Headers
matches cors.headers
. If so it adds Access-Control-Allow-Origin
, Access-Control-Allow-Methods
, and possibly Access-Control-Allow-Headers
, Access-Control-Allow-Credentials
, Access-Control-Max-Age
.
In other words to allow CORS for all requests, you do app->routes->to('cors.origin' => '...', ...);
, and app->routes->cors('/some/path')
for every preflighted request.
To give you an example here's an app that handles requests for two domains (e.g. a.example.com
and b.example.com
). When you open http://a.example.com, it does two requests to b.example.com
(a simple and preflighted):
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
plugin 'SecureCORS';
app->routes->to('cors.origin' => '*');
get '/' => sub ($c) {
$c->render;
} => 'index';
get '/simple' => sub ($c) {
$c->render(text => '');
};
app->routes->cors('/preflight');
del '/preflight' => {'cors.headers' => ''} => sub ($c) {
$c->render(text => '');
};
app->start;
__DATA__
@@ index.html.ep
<!doctype html>
<html>
<body>
<script>
fetch('http://b.example.com/simple')
.then(r => console.log(r))
.then(() =>
fetch('http://b.example.com/preflight', {method: 'DELETE'})
.then(r => console.log(r))
)
</script>
</body>
</html>
Files needed to run it under docker
can be found here.
Another issue here is that it makes sense to define cors.headers
for preflighted routes. Although it works anyway, it triggers a warning:
Use of uninitialized value $opt{"headers"} in split at /app/local/lib/perl5/Mojolicious/Plugin/SecureCORS.pm line 118.
And while the preflight route can gather the cors.*
options from the target route (if there's no cors.origin
on the preflight route), the target route can only rely on itself. That is, if there are no global/parent settings, and you define cors.origin
on the preflight route, you should also do so on the target route:
app->routes->cors('/preflight')->to('cors.origin' => '*');
del '/preflight' => {'cors.origin' => '*'} => sub ($c) {
$c->render(text => '');
};
(Make sure you disable cache when experimenting, preflight requests are cached.)