0

I am new to Mojolicious and using plugins especially SecureCORS. How can I allow CORS on POST requests?

I managed to allow CORS for GET with following lines:

use Mojolicious::Lite;

app->plugin('SecureCORS');
app->routes->to('cors.origin' => '*');

I thought 'cors.origin' => '*' is allowing CORS for all methods but it works only for GET.

Maybe there is another or better way to send the Access-Control-Allow-Origin header and allow all POST, GET, PUT, ...

brian d foy
  • 129,424
  • 31
  • 207
  • 592
WastedFreeTime
  • 195
  • 2
  • 3
  • 16

3 Answers3

3

Frankly said, I have not used SecureCors plugin but I faced the same problem of cross-origin problem while developing the REST API's in Mojolicious.

Note:- I am not using the Mojolicious::lite app.

You might need to do something more or less similar to this, under your startup subroutine (If not using Mojolicious::Lite).

$self->hook(after_dispatch => sub { 
    my $c = shift; 
    $c->res->headers->header('Access-Control-Allow-Origin' => '*'); 
    $c->res->headers->access_control_allow_origin('*');
    $c->res->headers->header('Access-Control-Allow-Methods' => 'GET, OPTIONS, POST, DELETE, PUT');
    $c->res->headers->header('Access-Control-Allow-Headers' => 'Content-Type' => 'application/x-www-form-urlencoded');

}); 

I hope it helps.

ash
  • 46
  • 2
  • 7
1

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.)

x-yuri
  • 16,722
  • 15
  • 114
  • 161
1

defined to allow CORS, either here or in the parent routes. to set CORS options for this route at least origin option must be.

$r->get(…, {'cors.origin' => '*'}, …);
$r->any(…)->to('cors.origin' => '*');

allow non-simple (with preflight) CORS on this route

$r->cors(…);