Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JQuery AJAX cross-site request under Laravel CSRF protection

I'm building a CMS-like web application using Laravel(back-end) and ReactJS with JQuery(front-end).

I decide to put the existing Web API into a separate domain(api.test.com), and my user interface is on the different domain(test.com).

On test.com, I launch an ajax request to api.test.com to modify some resource on the server:

  $.ajax({
    url: "api.test.com",
    method: 'POST',
    data: {...}
    success: function (no) {
    // ...
    }
  });

And of course it's illegal due to security problem. However, I can configure my web server:

For Nginx:

  add_header Access-Control-Allow-Origin http://test.com;
  add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE;
  add_header Access-Control-Allow-Headers X-Requested-With,X-CSRF-TOKEN,X-XSRF-TOKEN;

The Access-Control-Allow-Origin problem is solved perfectly but another problem occurs due to Laravel's CSRF protection...

Laravel requires a CSRF token included in the request(POST,PUT...which will modify the resource) by default.

In my case, I must generate csrf_token on api.test.com rather than test.com because different domain do not share the token.

I followed the User Guide of Laravel and added these code to my front-end:

  $.ajax({
    url: "api.test.com/token", // simply return csrf_token();
    method: "GET",
    success: function (token) {
      // Now I get the token
      _token = token;
    }.bind(this)
  });

and modify the previous request implementation:

  $.ajax({
    url: "api.test.com",
    method: 'POST',
    headers: {
      "X-CSRF-TOKEN": _token // Here I passed the token
    },
    data: {...}
    success: function (no) {
    // ...
    }
  });

But Laravel responses a status code of 500. Then I checked the VerifyCsrfToken.php:

protected function tokensMatch($request)
{
    $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
    if (!$token && $header = $request->header('X-XSRF-TOKEN')) {
        $token = $this->encrypter->decrypt($header);
    }
    // Log::info($request->session()->token() . " == $token");
    return Str::equals($request->session()->token(), $token);
}

The $token which I 'POST' to is different from what it was ($request->session()->token()).

I found that the validation tokens on server are different when calling $.ajax.

I try to put the two requests in the same session(by changing the cookie), but it's impossible.

I spent a lot of time to solve this problem but didn't work it out.

Have any idea or solution?

Thanks, Micooz

like image 478
Micooz Avatar asked Oct 19 '22 07:10

Micooz


1 Answers

Thank you for answering my question. I've considered disabling the CSRF protection to some URIs but I don't want to take these risk.

The key point of my question is that the $.ajax forgets carrying cookies before request, and resulting token validation failed.

Now I setup the JQuery Ajax, let it carry cookies before make a request.

  $.ajaxSetup({
    xhrFields: { withCredentials: true }
  });

and Nginx conf:

  add_header Access-Control-Allow-Credentials true;

BTW, it's not necessary to include the token in the data:{}(Form).

All the problems are settled and it works perfectly for me.

like image 51
Micooz Avatar answered Oct 21 '22 23:10

Micooz