Amon2-Plugin-Web-CSRFDefender

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

This plugin denies CSRF request.

Do not use this with [HTTP::Session2](https://metacpan.org/pod/HTTP::Session2). Because [HTTP::Session2](https://metacpan.org/pod/HTTP::Session2) has XSRF token management function by itself.

# METHODS

- $c->get\_csrf\_defender\_token()

    Get a CSRF defender token. This method is useful to add token for AJAX request.

- $c->validate\_csrf()

    You can validate CSRF token manually.

# PARAMETERS

- no\_validate\_hook

    Do not run validation automatically.

- no\_html\_filter

    Disable HTML rewriting filter. By default, CSRFDefender inserts XSRF token for each form element.

    It's very useful but it hits performance issue if your site is very high traffic.

- csrf\_token\_generator

lib/Amon2/Plugin/Web/CSRFDefender.pm  view on Meta::CPAN


    unless ($conf->{no_html_filter}) {
        $c->add_trigger(
            HTML_FILTER => sub {
                my ($self, $html) = @_;
                $html =~ s!($form_regexp)!qq{$1\n<input type="hidden" name="csrf_token" value="}.$self->get_csrf_defender_token().qq{" />}!ge;
                return $html;
            },
        );
    }
    unless ($conf->{no_validate_hook}) {
        $c->add_trigger(
            BEFORE_DISPATCH => sub {
                my $self = shift;
                if (not $self->validate_csrf()) {
                    return $self->create_response(
                        403,
                        [
                            'Content-Type'   => 'text/html',
                            'Content-Length' => length($ERROR_HTML)
                        ],
                        $ERROR_HTML
                    );
                } else {
                    return;

lib/Amon2/Plugin/Web/CSRFDefender.pm  view on Meta::CPAN

    Amon2::Util::add_method($c, 'get_csrf_defender_token', sub {
        my $self = shift;
        if (my $token = $self->session->get('csrf_token')) {
            $token;
        } else {
            my $token = $csrf_token_generator->($self);
            $self->session->set('csrf_token' => $token);
            $token;
        }
    });
    Amon2::Util::add_method($c, 'validate_csrf', \&validate_csrf);
}

sub validate_csrf {
    my $self = shift;

    if ( $self->req->method eq 'POST' ) {
        my $r_token       = $self->req->param('csrf_token') || $self->req->header('x-csrf-token');
        my $session_token = $self->session->get('csrf_token');
        if ( !$r_token || !$session_token || ( $r_token ne $session_token ) ) {
            return 0; # bad
        }
    }
    return 1; # good

lib/Amon2/Plugin/Web/CSRFDefender.pm  view on Meta::CPAN

Do not use this with L<HTTP::Session2>. Because L<HTTP::Session2> has XSRF token management function by itself.

=head1 METHODS

=over 4

=item $c->get_csrf_defender_token()

Get a CSRF defender token. This method is useful to add token for AJAX request.

=item $c->validate_csrf()

You can validate CSRF token manually.

=back

=head1 PARAMETERS

=over 4

=item no_validate_hook

Do not run validation automatically.

=item no_html_filter

Disable HTML rewriting filter. By default, CSRFDefender inserts XSRF token for each form element.

It's very useful but it hits performance issue if your site is very high traffic.

=item csrf_token_generator

t/011_csrf_defender_manual.t  view on Meta::CPAN


our $COMMIT;

{
    package MyApp::Web;
    use Amon2::Lite;

    sub load_config { +{} }

    __PACKAGE__->load_plugins(
        'Web::CSRFDefender', {no_validate_hook => 1}
    );

    get '/form' => sub {
        my $c = shift;
        $c->render('form.tt');
    };
    post '/do' => sub {
        my $c = shift;
        $COMMIT++;
        $c->redirect('/finished');
    };
    post '/do2' => sub {
        my $c = shift;
        if ($c->validate_csrf) {
            $c->create_response(200, [], ['valid token']);
        } else {
            $c->create_response(403, [], ['denied']);
        }
    };
    get '/finished' => sub {
        Plack::Response->new(200, [], ['Finished']);
    };
    get '/get_csrf_defender_token' => sub {
        my $c = shift;

t/011_csrf_defender_manual.t  view on Meta::CPAN

    test_psgi
        app => $app,
        client => sub {
            my $cb = shift;
            my $res = $cb->(HTTP::Request->new(POST => 'http://localhost/do'));
            is $res->code, '302';
            is $COMMIT, 1;
        };
};

subtest 'but you can validate manually' => sub {
    local $COMMIT = 0;
    test_psgi
        app => $app,
        client => sub {
            my $cb = shift;
            my $res = $cb->(HTTP::Request->new(POST => 'http://localhost/do2'));
            is $res->code, '403';
            is $COMMIT, 0;
        };
};



( run in 0.543 second using v1.01-cache-2.11-cpan-a5abf4f5562 )