We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

csrf

hi,

i'm using the vokuro as an example for creating a login form + action but the login failed at csrf validation. but if i use vokuro it's not a problem. i suspect it's something to do with the getSessionToken() and getToken() function because they return different result where it's should be the same.

any ideas? thanks for any input



3.4k

i've tried the above link, using:

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

still doesn't work because getSessionToken() and getToken() function return different values where they should be the same.



19.2k
edited Jul '14

You should assign CSRF field value from the view:

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


3.4k

i did that at the first time (from vokuro) and then tried jc's suggestion. both didn't work.



19.2k
edited Jun '14

In Vokuro it's set like that by default:

// LoginForm.php

//CSRF
$csrf = new Hidden('csrf');
$csrf->addValidator(
    new Identical(array(
        'value' => $this->security->getSessionToken(),
        'message' => 'CSRF validation failed'
    ))
);
$this->add($csrf);
// login.volt

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

and it's working just fine. Also, are you using template inheritance?



3.4k

Yes, I am. Why? does that make a difference?



19.2k

When I've tried to use template inheritance, I'm too experienced CSRF validation problems for some reason, anyway I haven't had time to dig in that issue, so just refused to use template inheritance. I think that the problem covers in the internal process of inheriting, something like that: token in view is generated for the base template, while token in form validation is generated for the child template, and because of that they are different and validation fails. Anyway that's just a sleepy thoughts, don't get it serious.



3.4k

chebureque, you're probably right. so is this a bug? is there a way around it?

The reason why this code does not work:

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

is that when you execute $this->security->getToken() a NEW value is generated and saved in the session, so the $this->security->getSessionToken() will return the new one and not the one generated on the previous page, where you submitted the form.

On the other hand the html{{ form.render('csrf', ['value': security.getToken()]) }} works because the template's code is run after the form's code so no new value is generated. But works only when you use this method once/page.

So my workaround is:

 protected static $csrf;
  protected function addCsrf(){
    if(!self::$csrf) {
      self::$csrf = array(
        'old'=> $this->security->getSessionToken(),
        'new'=> $this->security->getToken(),
      );
     }

    $field = new Hidden('csrf', array('value'=> self::$csrf['new']));
    $field->addValidator(
      new Identical(array(
        'value' =>  self::$csrf['old'],
        'message' => 'Refresh detected. CSRF Verification Failed'
      ))
    );
    $this->add($field);
  }
</code> 
edited Jun '15

My be your session path wrong. You can check log if your show

Unknown: Failed to write session data (files). Please verify that the current setting of session.save_path is correct (/var/lib/php/session) in Unknown on line 0" while reading upstream

You can check log if your show like that. Just config right session.save_path it will work fine.


If it work fine for first time but not work any more. It may be because Phalcon Form. You can go to action have error(ex: loginAction) and unset $_POST['csrf']

public function loginAction()
    {
        $form = new LoginForm();

        try {

            if (!$this->request->isPost()) {

                if ($this->auth->hasRememberMe()) {
                    return $this->auth->loginWithRememberMe();
                }
            } else {
                if ($form->isValid($this->request->getPost()) == true) {

                    $this->auth->check(array(
                        'email' => $this->request->getPost('email', 'email'),
                        'password' => $this->request->getPost('password', 'striptags'),
                        'remember' => $this->request->getPost('remember')
                    ));

                    return $this->response->redirect('users');
                }
            }
        } catch (AuthException $e) {
            $this->flash->error($e->getMessage());
            return;
        }

        //fix for "CSRF validation failed error"
        unset($_POST['csrf']);

        $this->view->form = $form;
    }

Maybe it helps!

i've tried the above link, using:

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

still doesn't work because getSessionToken() and getToken() function return different values where they should be the same.