How to check the CSRF token?

I notice that in the example Vökuró, there is CSRF:

// CSRF
$csrf = new Hidden('csrf');

$csrf->addValidator(new Identical(array(
'value' => $this->security->getSessionToken(),
'message' => 'CSRF validation failed'
)));

$this->add($csrf);


// in volt:
{{ form.render('csrf', ['value': security.getToken()]) }}

According to the manual, we should check if the CSRF token is valid:

if ($this->request->isPost()) {
  if ($this->security->checkToken()) {
  // The token is OK
  }
}

But in the example Vökuró, I didn't see any code that can check the CSRF token... why?

Vokuro is using an older version of phalcon, and if you try in the new version csrf always fails.



5.9k
edited Apr '16

This should be helpful (Phalcon 2.0.9):

// In services.php 
$di->setShared('security', function () {

    $security = new Security();

    // set Work factor (how many times we go through)
    $security->setWorkFactor(12); // can be a number from 1-12

    // set Default Hash
    $security->setDefaultHash(Security::CRYPT_BLOWFISH_Y); // choose default hash

    return $security;

});

// In YourForm.php
class YourForm extends Form {

    // Holds token key/name
    protected $_csrf;

    public function initialize() {

        // Some other elements...

        // CSRF protection
        $csrf = new Hidden($this->getCsrfName());
        $csrf->setDefault($this->security->getToken())
                ->addValidator(new Identical([
                 'accepted'   => $this->security->checkToken(),
                 'message' => 'CSRF forgery detected!'
             ]));
        $this->add($csrf);
    }

    // Generates CSRF token key
    public function getCsrfName()
    {
        if (empty($this->_csrf)) {
            $this->_csrf = $this->security->getTokenKey();
        }

        return $this->_csrf;
    }
}

// In volt view 

{{ form.render(form.getCsrfName()) }}

I have this code in FormBase.php any each new form just extends from this class.



8.0k

That works! Thank you!



8.0k

Hi, @mraspor

I'm not sure if the CSRF token cheching really works. following is the code

services.php:

$di->setShared('security', function () {
    $security = new Security();
    $security->setWorkFactor(12);
    $security->setDefaultHash(Security::CRYPT_BLOWFISH_Y); // choose default hash
    return $security;
});

$di->set('crypt', function () use ($config) {
    $crypt = new Crypt();
    $crypt->setKey($config->application->cryptSalt);
    return $crypt;
});

FormBase.php and OtherForm.php, just like what you show

class FormBase extends Form
{
    protected $_csrf;

    public function initialize()
    {
        $csrf = new Hidden($this->getCsrfName());
        //$csrf->clear();
        $csrf->setDefault($this->security->getToken())
            ->addValidator(new Identical([
                'accepted'   => $this->security->checkToken(),
                'message' => 'CSRF forgery detected!'
            ]));
        //var_dump($this->security->getToken());
        $this->add($csrf);
    }

    // Generates CSRF token key
    public function getCsrfName()
    {
        if (empty($this->_csrf)) {
            $this->_csrf = $this->security->getTokenKey();
        }

        return $this->_csrf;
    }
}

//other forms
class RegisterForm extends FormBase
{
    public function initialize($entity = null)
    {
        parent::initialize();
    }
}

And in the controller:

$form = new RegisterForm();
if ($this->request->isPost()) {
    if ($form->isValid($this->request->getPost()) == false) {
        foreach ($form->getMessages() as $message) {
            $this->flash->error((string) $message);
        }
    } else {
        $this->flash->success('ok');
        var_dump($this->request->getPost());
        var_dump($this->security->checkToken());
    }
}

when I fill the form, and manually modify the token value, and submit....It just says 'ok', seems the addValidator(new Identical(...)) can't work!

why?



4.4k
edited Apr '16

Below is how I handle this CSRF problem:

1, in myForm class:

public static function buildResetPasswordForm()
    {
        $form = new Form();
        $form->add(new Password('password1'));
        $form->add(new Password('password2'));
        $form->add(new Hidden('csrf',['name'=>SecurityFacade::getTokenKey(),'value'=>SecurityFacade::getToken()]));
        $form->add(new Submit('重置'));
        return $form;
    }

2, in controller file :

if($this->request->isPost() && SecurityFacade::checkToken()){
            $data = $this->request->getPost();
            if($this->isTwoPasswordSame($data)){
                $user->changePassword($data['password1']);
                User::loginByUser($user);

                return $this->redirectByRoute(['for'=>'home']);
//                return $this->redirectByRoute(['for'=>'login']);
            }
            FlashFacade::error('两次密码输入不一致');
        }
        $this->view->form = myForm::buildResetPasswordForm();

3, in volt file:

    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">密码重置</div>
                <div class="panel-body">
                    {{ flash.output() }}
                    {{ form("method": "post","class":"form-horizontal","role":"form") }}


                    <div class="form-group">
                        <label class="col-md-4 control-label">输入密码</label>
                        <div class="col-md-6">
                            {{ form.render('password1',['class':"form-control"]) }}
                        </div>
                    </div>

                    <div class="form-group">
                        <label class="col-md-4 control-label">重新输入密码</label>
                        <div class="col-md-6">
                            {{ form.render('password2',['class':"form-control"]) }}
                        </div>
                    </div>
                    <div class="form-group">
                        <div class="col-md-6 col-md-offset-4">
                            {{ form.render('重置',['class':"btn btn-primary"]) }}
                        </div>
                    </div>
                    {{ form.render('csrf') }}

                    {{ endform() }}
                </div>
            </div>
        </div>
    </div>

note: I use the facade patterm like laravel, see more: https://forum.phalconphp.com/discussion/10371/borrow-facade-design-pattern-from-laravel



8.0k

@huoybb 谢谢!你这个相当于扩展了phalcon的验证类?看得不是很懂,我要Phalcon原始的验证类



4.4k

看手册啦! 注意,Session和Security的设置需要事先设置好。

https://docs.phalconphp.com/en/latest/reference/security.html#cross-site-request-forgery-csrf-protection

@huoybb 谢谢!你这个相当于扩展了phalcon的验证类?看得不是很懂,我要Phalcon原始的验证类