NAME

Mojolicious::Plugin::CSRF - Cross Site Request Forgery (CSRF) "prevention" Mojolicious plugin

VERSION

version 1.05

SYNOPSIS

# Simple Mojolicious
$app->plugin('CSRF');

my $token = $app->csrf->token;                 # returns the current token
my $url   = $app->csrf->url_for('/some/path'); # returns a Mojo::URL object

$app->csrf->delete_token;

$app->csrf->setup;
my $result = $app->csrf->check;

# Customized Mojolicious
use Crypt::URandom;
use Mojo::DOM;
use Mojo::Util;
$app->plugin( CSRF => {
    generate_token => sub { unpack( 'H*', Crypt::URandom::urandom(16) ) },
    token_name     => 'csrf_token',
    header         => 'X-CSRF-Token',
    methods        => [ qw( POST PUT DELETE PATCH ) ],
    include        => [ '^/' ],
    exclude        => [ '^/api/[^/]+/user/log(?:in|out)/test$' ],

    on_success => sub {
        my ($c) = @_;
        $c->log->info('CSRF check success');
        return 1;
    },

    on_failure => sub {
        my ($c) = @_;
        $c->reply->exception(
            'Access Forbidden: CSRF check failure',
            { status => 403 },
        );
        return 0;
    },

    hooks => [
        before_routes => sub {
            my ($c) = @_;
            $c->csrf->setup;
            $c->csrf->check;
        },

        after_render => sub {
            my ( $c, $output, $format ) = @_;

            if ( $format eq 'html' and $$output ) {
                my $dom = Mojo::DOM->new(
                    Mojo::Util::decode( 'UTF-8', $$output )
                );
                my $forms = $dom->find('form[method="post"]');

                if ( $forms->size ) {
                    $forms->each( sub {
                        $_->append_content(
                            '<input type="hidden" ' .
                                'name="'  . $c->csrf->token_name . '" ' .
                                'value="' . $c->csrf->token . '">'
                        );
                    } );
                    $$output = Mojo::Util::encode( 'UTF-8', $dom->to_string );
                }
            }
        },
    ],
} );

# Mojolicious::Lite
plugin('CSRF');

DESCRIPTION

This module is a Mojolicious plugin for Cross Site Request Forgery (CSRF) "prevention" (theoretically; if used correctly; caveat emptor).

By default, when used, the plugin will cause requests methods that traditionally contain data-changing actions (i.e. POST, PUT, etc.) to check a generated session token against a token from a form value, URL parameter, or HTTP header. On failure, a Mojo::Exception is thrown.

METHODS

The plugin provides a csrf helper from which some methods can be called.

token

This method will return the current token. If there is no current token, this method will first call the generate_token method, store the new token in the Mojolicious session, and then return the new token.

my $token = $app->csrf->token;

url_for

This is a wrapper around url_for from Mojolicious::Plugin::DefaultHelpers, returning a Mojo::URL object with the current token merged as a parameter.

# returns a Mojo::URL object
my $url  = $app->csrf->url_for('/some/path');
my $url2 = $app->csrf->url_for('/some/other/path')->query({ answer => 42 });

delete_token

This method deletes the current token from the Mojolicious session.

$app->csrf->delete_token;

setup

This method should be called prior to rendering a page that precedes a request where check is called. (All this does is set the HTTP header.)

$app->csrf->setup;

check

This method checks the current token (saved in the Mojolicious session) against a token value from a form value, URL parameter, or HTTP header.

my $result = $app->csrf->check;

The method will call on_success or on_failure after a check.

SETTINGS

Almost everything can be customized from the plugin call by providing a hashref of stuff.

generate_token

This is a code reference that when called is expected to generate a new token and return it (though not save it). This subroutine is called by token when it needs to generate a token.

token_name

This is the form/URL parameter name containing the comparison token. By default, it's "csrf_token".

This is the HTTP header name containing the comparison token. By default, it's "X-CSRF-Token".

methods

These are the methods where a comparison check will be performed. You can specify the set of methods in an arrayref of strings. By default, it's:

[ qw( POST PUT DELETE PATCH ) ]

If you set "any", then all methods are checked:

['any']

include

This is an arrayref of strings of regular expressions representing URL paths to include checks on. If not defined, then all paths are checked.

exclude

This is an arrayref of strings of regular expressions representing URL paths to exclude checks on.

on_success

This is the code reference called when a check is successful. It'll be passed the application object.

on_success => sub {
    my ($c) = @_;
    $c->log->info('CSRF check success');
    return 1;
},

on_failure

This is the code reference called when a check fails. It'll be passed the application object.

on_failure => sub {
    my ($c) = @_;
    $c->reply->exception(
        'Access Forbidden: CSRF check failure',
        { status => 403 },
    );
    return 0;
},

hooks

This is an arrayref of hook names and code references the plugin will install during it's registration. You could easily (and probably more cleanly) just do this yourself as you prefer; but by default, this plugin will set a before_routes hook and a after_render hook as follows:

hooks => [
    before_routes => sub {
        my ($c) = @_;
        $c->csrf->setup;
        $c->csrf->check;
    },

    after_render => sub {
        my ( $c, $output, $format ) = @_;

        if ( $format eq 'html' and $$output ) {
            my $dom = Mojo::DOM->new(
                Mojo::Util::decode( 'UTF-8', $$output )
            );
            my $forms = $dom->find('form[method="post"]');

            if ( $forms->size ) {
                $forms->each( sub {
                    $_->append_content(
                        '<input type="hidden" ' .
                            'name="'  . $c->csrf->token_name . '" ' .
                            'value="' . $c->csrf->token . '">'
                    );
                } );
                $$output = Mojo::Util::encode( 'UTF-8', $dom->to_string );
            }
        }
    },
],

SEE ALSO

Mojolicious, Mojolicious::Plugin, Mojolicious::Plugin::CSRFProtect, Mojolicious::Plugin::DeCSRF, Mojolicious::Plugin::CSRFDefender.

You can also look for additional information at:

AUTHOR

Gryphon Shafer <gryphon@cpan.org>

COPYRIGHT AND LICENSE

This software is Copyright (c) 2025-2050 by Gryphon Shafer.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)