preaction / Yancy

The Best Web Framework Deserves the Best Content Management System
http://preaction.me/yancy/
Other
54 stars 21 forks source link

How to debug CSRF token validation failed error ? #95

Closed pavelsr closed 4 years ago

pavelsr commented 4 years ago

I got CSRF error during form processing.

2020-04-10 21:59:55.42484] [3683] [debug] [9d11dd05] Routing to a callback
[2020-04-10 21:59:55.42732] [3683] [debug] [9d11dd05] Routing to controller "Yancy::Controller::Yancy" and action "set"
[2020-04-10 21:59:55.42811] [3683] [error] CSRF token validation failed
[2020-04-10 21:59:55.42968] [3683] [debug] [9d11dd05] Rendering cached template "yancy/set.html.ep"
[2020-04-10 21:59:55.43005] [3683] [debug] [9d11dd05] 400 Bad Request (0.005673s, 176.274/s)

Seems like error from this string.

How to debug the reason?

preaction commented 4 years ago

Oops, I can save you some time: The Form plugin isn't adding the CSRF token field.


Edit: No, I'm wrong, the form plugin is adding a CSRF token, so your form must not have one. You have to add one using the csrf_field helper or the csrf_token helper.

pavelsr commented 4 years ago

Sorry, I didn't understand. Where I need to add csrf_field or csrf_token ?

My form processor controller looks like

my $user_route = app->routes->under( '/my', sub {
    my ( $c ) = @_;
    my $user = $c->yancy->auth->current_user;
    $c->stash( user_id => $user->{id} );
    return 1;
} );
$user_route->post( 'cars/create' )->to( 'yancy#set', schema => 'cars' ); 
preaction commented 4 years ago

If you use csrf_field, it needs to be in the form so it gets sent with the request, like:

%= form_for carscreate => begin
    %= csrf_field
    ... the rest of the form here ...
% end

If you can't use csrf_field, if you're submitting the form through JS or something, you can get the token itself by using csrf_token:

% use Mojo::JSON qw( to_json );
%= javascript begin
    window.csrf_token = <%= to_json csrf_token() %>
% end
pavelsr commented 4 years ago

Ok, I understand that. But problem is that I generate form using Yancy::Plugin::Form::form_for

Simple demo of that error: https://gist.github.com/pavelsr/ab24ac5972df2ac9b6983e3b501252f6

I think something wrong with with CSRF validation code or with yancy/form/bootstrap4/form.html.ep

UPD: As quick fix I have to submit form data as JSON, but still please look the demo.

preaction commented 4 years ago

... Oh wow. It took me a couple hours, but I found the problem: The docs say to do app->yancy->form->form_for( ... ), but that creates a new, blank controller object. So the form generates a new CSRF token for that new controller, which is not the correct CSRF token in the real controller object's session...

Changing the index.html.ep template to use $c->yancy->form->form_for(...) fixes the problem. I'm going to add some notes to the docs, fix some docs that have app->yancy->form instead of $c->yancy->form, and add a check in form_for that verifies that the controller has an actual request when generating a CSRF token (and issuing a warning if it does not). I'll also add a way to disable CSRF just in case one finds the need to...