How to pass along CSRF token in an AJAX post request for a form?

66,337

Solution 1

Ok, after fighting this for a few hours and trying to decrypt Play's frequently-lacking-context-Documentation on the subject, I've got it.

So, from their docs:

To allow simple protection for non browser requests, Play only checks requests with cookies in the header. If you are making requests with AJAX, you can place the CSRF token in the HTML page, and then add it to the request using the Csrf-Token header.

And then there's no code or example. Thanks Play. Very descriptive. Anyway, here's how:

in your view.html.formTemplate you might write in IntelliJ:

@()
<form method="post" id="myForm" action="someURL">

@helper.CSRF.formField  <!-- This auto-generates a token for you -->
  <input type="text" id="sometext">
  <button type="submit"> Submit! </button>

</form>

And this will render like this when delivered to the client:

<form method="post" id="myForm" action="someURL">

<input name="csrfToken" value="5965f0d244b7d32b334eff840...etc" type="hidden">
  <input type="text" id="sometext">
  <button type="submit"> Submit! </button>

</form>

Ok, almost there, now we have to create our AJAX call. I have all of mine in a separate main.js file, but you could also put this in your view.html.formTemplate if you want.

$(document).on('submit', '#myForm', function (event) {

 event.preventDefault();
   var data = {
    myTextToPass: $('#sometext').val()
   }
 // LOOK AT ME! BETWEEN HERE AND
 var token =  $('input[name="csrfToken"]').attr('value')
    $.ajaxSetup({
        beforeSend: function(xhr) {
            xhr.setRequestHeader('Csrf-Token', token);
        }
    });
// HERE
 var route = jsRoutes.controllers.DashboardController.postNewProject()
 $.ajax({
    url: route.url,
    type: route.type,
    data : JSON.stringify(data),
    contentType : 'application/json',
    success: function (data) { ...      },
    error: function (data) { ...  }
        })

});

With this line: var token = $('input[name="csrfToken"]').attr('value') You are plucking out the CSRF token auto generated in your form field and grabbing its value in a var to be used in your Javascript.

The other important chunk from all that AJAX is here:

$.ajaxSetup({
            beforeSend: function(xhr) {
                xhr.setRequestHeader('Csrf-Token', token);
            }
        });

Using the $.ajaxSetup, you can set what's in the header. This is what you have to infer from their documentation:

add it to the request using the Csrf-Token header.

Good luck! Let me know if this is clear.


Note: when using lusca, use X-CSRF-Token instead of Csrf-Token.

Solution 2

From JSP

<form method="post" id="myForm" action="someURL">
    <input name="csrfToken" value="5965f0d244b7d32b334eff840...etc" type="hidden">    
</form>

This is the simplest way that worked for me after struggling for 3hrs, just get the token from input hidden field like this and while doing the AJAX request to just need to pass this token in header as follows:-

From JQuery

var token =  $('input[name="csrfToken"]').attr('value'); 

From plain Javascript

var token = document.getElementsByName("csrfToken").value;

Final AJAX Request

$.ajax({
          url: route.url,
          data : JSON.stringify(data),
          method : 'POST',
          headers: {
                        'X-CSRFToken': token 
                   },
          success: function (data) { ...      },
          error: function (data) { ...  }
});

Now you don't need to disable crsf security in web config, and also this will not give you 405( Method Not Allowed) error on console.

Hope this will help people..!!

Solution 3

add it to the request using the Csrf-Token header.

Thanks NateH06 for the header name! I was trying to send csrf token for a "delete" button with an ajax function call and I was stuck on the following:

@import helper._
....
<button id="deleteBookBtn" class="btn btn-danger"
        data-csrf-name="@helper.CSRF.getToken.name"
        data-csrf-value="@helper.CSRF.getToken.value"
        data-delete-url="@routes.BooksController.destroy(book.id)"
        data-redirect-url="@routes.HomeController.index()">Delete</button>

I wasn't able to add online js within the onclick() event because of a CSP set on play 2.6 too.

Refused to execute inline event handler because it violates the following Content Security Policy directive: "default-src 'self'".

And on the JS file:

function sendDeleteRequest(event) {
  url = event.target.getAttribute("data-delete-url")
  redirect = event.target.getAttribute("data-redirect-url")
  csrfTokenName = event.target.getAttribute("data-csrf-name")
  csrfTokenValue = event.target.getAttribute("data-csrf-value")
  $.ajax({
    url: url,
    method: "DELETE",
    beforeSend: function(request) {
      //'Csrf-Token' is the expected header name, not $csrfTokenName
      request.setRequestHeader(/*$csrfTokenName*/'Csrf-Token', csrfTokenValue);
    },
    success: function() {
      window.location = redirect;
    },
    error: function() {
      window.location.reload();
    }
  })
}

var deleteBookBtn = document.getElementById("deleteBookBtn");
if(deleteBookBtn) {
    deleteBookBtn.addEventListener("click", sendDeleteRequest);
}

After setting the header name as 'Csrf-Token' the ajax call works perfectly!

Solution 4

In case it's of use to anyone else who is googling around trying to understand why they can't get the correct token to appear.... I've been struggling with the same issue for a Play backend/React frontend combination - and hence unable to (easily) use the token-in-htmlpage technique I eventually came across another solution for setting the current token in a cookie... simply add:

play.filters.csrf {
  cookie.name = "csrftoken"
}

to application.conf and the csrftoken cookie will be set to the token. I then used https://www.npmjs.com/package/js-cookie to grab the value in my JS code and send it back in the request header - not including the code here as it's React not jQuery as for the OP and don't want to confuse matters.

Share:
66,337
NateH06
Author by

NateH06

Updated on July 09, 2022

Comments

  • NateH06
    NateH06 almost 2 years

    I'm using Scala Play! 2.6 Framework, but that may not be the issue. I'm using their Javascript routing - and it seems to work ok, but it's having issues. I have a form, which when rendered produces this, with a CSRF token:

    <form method="post" id="myForm" action="someURL">
    
    <input name="csrfToken" value="5965f0d244b7d32b334eff840...etc" type="hidden">
      <input type="text" id="sometext">
      <button type="submit"> Submit! </button>
    
    </form>
    

    And here's roughly, my AJAX:

    $(document).on('submit', '#myForm', function (event) {
    
     event.preventDefault();
       var data = {
        textvalue: $('#sometext').val()
       }
     var route = jsRoutes.controllers.DashboardController.postNewProject()
     $.ajax({
        url: route.url,
        type: route.type,
        data : JSON.stringify(data),
        contentType : 'application/json',
        success: function (data) { ...      },
        error: function (data) { ...  }
            })
    
    });
    

    But when I post this, I am getting an UNAUTHORIZED response back from my Server, and my console in IntelliJ is telling me the CSRF check is failing. How would I pass along the CSRF token in the request?

  • NateH06
    NateH06 over 6 years
    Thanks! I pretty much wrote it that way because I know I'd need to one day reference it again myself.
  • Mr. Pyramid
    Mr. Pyramid over 6 years
    Your answer is seriously helping others a lot! :)
  • Marcus Persson
    Marcus Persson about 6 years
    @NateH06 What if you don't use "<form method="post" id="myForm" action="someURL">" and "@helper.CSRF.formField", but instead use "@CSRF(route.Something.something(something))" where "something" is a javascript variable that should be parsed into "@CSRF(route.Something.something(something))"? Is this even possible?
  • NateH06
    NateH06 about 6 years
    If you're trying to roll your own token using JS, I would avoid using their @CSRF helper call at all, I'm not sure if it takes parameters. Just generate the HTML on your own and insert the value as you see fit. <input name="yourCsrfToken" value="route.Something(something) etc" type="hidden"> and then change the above var set to: var token = $('input[name="yourCsrfToken"]').attr('value')
  • Alex Pop
    Alex Pop about 5 years
    Works perfectly! I had to add 'X-CSRF-Token', because I am using lusca
  • Mark Lewis
    Mark Lewis almost 5 years
    I did this and I still ran into problems when making multiple Ajax calls for a SPA. To get my code to work I needed the approach described at stackoverflow.com/questions/46456399/….
  • Daniel Donnelly
    Daniel Donnelly over 3 years
    This does help, but only if 'X-CSRFToken' is used (without the second dash that is)!