Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Phoenix - invald CSRF on AJAX post

I'm learning to use the Phoenix framework, and I'm trying to do an AJAX post to a controller action - however, I'm running into a problem with the CSRF protection.

For starters, I'm not using a form - just want to pass text from an input to the controller:

<input type="text" id="raw-input" />
<button id="send-button">Send it!</button>

<script>
$("#send-button").click(function(){
    var input = $("#raw-input").val();

    $.ajax({
        url: "/test/process",
        type: "POST",
        dataType: "json",
        beforeSend: function(xhr) {xhr.setRequestHeader("X-CSRF-Token", $("meta[name='csrf-token']").attr("content"))},
        data: {"input" : input},
        success: function(response){
            console.log(response);
        }
    });
});
</script>

The controller (not worried about doing anything input yet... just want to verify a successful post!):

def process(conn, %{"input" => input}) do
    IO.puts "got it!"
end

And the router:

post "/test/process", TestController, :process

I pretty much lifted the $.ajax call from a Rails app where it was working fine, but it's not doing the trick here - running this returns a 403 error and logs (Plug.CSRFProtection.InvalidCSRFTokenError) invalid CSRF (Cross Site Request Forgery) token, make sure all requests include a valid '_csrf_token' param or 'x-csrf-token' header.

Can anyone offer any guidance? Thank you!

like image 204
skwidbreth Avatar asked Nov 14 '16 20:11

skwidbreth


1 Answers

This is because Phoenix does not create a meta tag with the CSRF token by default. They're only included in forms generated by Phoenix's helper functions, and they're in a hidden input.

To get a CSRF token programatically in Phoenix, you can call Plug.CSRFProtection.get_csrf_token/0. There are many ways to pass this to your JS. You can add a meta tag to your layout to include it in every page but that might not be very efficient since it'll be generated for all pages. You can also just store it in a JS variable in the views that you require them in:

<input type="text" id="raw-input" />
<button id="send-button">Send it!</button>

<script>
$("#send-button").click(function(){
    var CSRF_TOKEN = <%= raw Poison.encode!(Plug.CSRFProtection.get_csrf_token()) %>;
    var input = $("#raw-input").val();

    $.ajax({
        url: "/test/process",
        type: "POST",
        dataType: "json",
        beforeSend: function(xhr) {
            xhr.setRequestHeader("X-CSRF-Token", CSRF_TOKEN);
        },
        data: {"input" : input},
        success: function(response){
            console.log(response);
        }
    });
});
</script>
like image 176
Dogbert Avatar answered Oct 03 '22 02:10

Dogbert