How to check the CSRF token?

I want to implement fields and validations for CSRF using Forms. For example, it looks like this.

// myForm
//$csrf = new Hidden($this->security->getTokenKey());
$csrf = new Hidden('csrf');
$csrf->addValidator(
  new Identical([
    //'value' => $this->security->getToken(),
    'value' => $this->security->getSessionToken(),
    'message'  => 'CSRF detect'
    ])
  );
$this->add($csrf);

// volt
{# {{ form.render('csrf') }} #}
<!--
<input type="hidden" name="csrf" value="{{ security.getSessionToken() }}" />
-->
<input type="hidden" name="csrf" value="{{ security.getToken() }}" />

// controller
if (!$this->security->checkToken('csrf')) {
  // abort
} else
if ($this->request->isPost()) {
  $form = new myForm();
  $data = $this->request->getPost();
  if ($form->isValid($data)) {
    // valid data
  } else {
    // invalid
  }
}
//$this->security->getToken();
$form = new myForm($foo);
$this->view->setVar('form', $form);

But it doesn't quite work. For example, if I use getTokenKey() in myForm, it will become a new key name when I do new myForm() for validation, so isValid() will always return false. Similarly, if I use getToken(), the token value will also change, so isValid() never return true. I tried "'accepted' => $this->security->checkToken()", but I couldn't pass it because maybe my something wrong.

I can't proceed further with this, so I thought about another way.

The value changes each time getTokenKey() is called, so the key value is fixed and the token value uses the session. However, it is necessary to call getToken() somewhere, so I have no choice but to call it at the end and set it.

Also, if I create an element using render() in the view, the contents of $form will have a new token value at the time of setVar(), but the old token value will be displayed. However, this hasn't been fully validated and may be due to my environment (eg using firefox or something else). If you write it in HTML without using render(), it seems to work.

In this case, it doesn't seem to make sense to define CSRF fields in myForm. And, instead of isValid(), if I check CSRF using checkToken() by myself in the controller, it will be better without fixing the key name and reducing the security strength.

When I searched, I found that I made a base class of form and prepared a property of key name so that it would not be changed carelessly, but I feel that "the framework should prepare" But what do you guys think? I would like to have getSessionTokenKey() as well as getSessionToken(). Alternatively, checkToken() may accept a flag that uses the session value, or checkSessionToken() may be present.

If possible, I would like to define the validation in myForm (without fixing the key name, of course), and just use isValid(), and in view, I would like to use form.render('csrf').

Could anyone please tell me the best practices?

I'm not a huge fan of Phalcon's built-in CSRF component for the same reasons - a token is only valid once per session. Multiple page loads invalidate the first token generated.

For that reason, I built my own that only generates the token and key once per session and re-uses it.



2.6k

I also think that the current built-in CSRF component of Phalcon can only be used in limited situations.

Today, it can be said that CSRF measures should be implemented on all input forms. I think site-wide tokens are one good solution, but I would love tokens tied to individual pages (or controllers or actions). (I don't feel the need to post form data from multiple same action pages in the same browser, and I think that if you really need it, you should remove CSRF.)

Similarly, I would like you to improve the cooperation with validation which does not work well in Forms at present.

By the way, when I query the server with AJAX in a certain form, I have succeeded in checking the token value of the form without changing the token value.

$this->security->checkToken(null, null, false)

So I want you to keep like this.

I'm not a huge fan of Phalcon's built-in CSRF component for the same reasons - a token is only valid once per session. Multiple page loads invalidate the first token generated.

For that reason, I built my own that only generates the token and key once per session and re-uses it.



5.8k

for me i created a class extends Phalcon\Security and replaced the security service with it

i remember getting the idea from here somewhere but couldn't find the thread anyway you can try this

TokenManager.php

class TokenManager extends \Phalcon\Security
{
    public function getToken()
    {
        $token = $this->getSessionToken();

        if (!$token) {
            return parent::getToken();
        }

        return $token;
    }

    public function getTokenKey()
    {
        $container = $this->getDI();

        $session = $container->getShared("session");

        $tokenKey = $session->get($this->_tokenKeySessionID);

        if (!$tokenKey) {
            return parent::getTokenKey();
        }

        return $tokenKey;
    }
}

services.php

$di->setShared('security', function () {
    return new TokenManager;
});

if i'm using html forms:

<input type="hidden" name="<?= $this->security->getTokenKey() ?>" value="<?= $this->security->getToken() ?>"/>

then in controller

// html form
$this->security->checkToken(null, null, false);

// javascript ( fetch, axios, etc... )
$this->security->checkToken($this->request->getPost('tokenKey'), $this->request->getPost('token'), false);


2.6k

hi Thanks for the reply. Since MVC increases the number of files even if it is free, I do not want to increase the number of files if possible, so I would like to avoid the suggested method or the method of creating a base class of form. I will. (If it is unavoidable, I will use it.)

I'm sorry to the people who make it, but I still can not wipe the impression that the implementation of Forms and Security class is incomplete.

for me i created a class extends Phalcon\Security and replaced the security service with it

i remember getting the idea from here somewhere but couldn't find the thread anyway you can try this

TokenManager.php

class TokenManager extends \Phalcon\Security
{
   public function getToken()
   {
       $token = $this->getSessionToken();

       if (!$token) {
           return parent::getToken();
       }

       return $token;
   }

   public function getTokenKey()
   {
       $container = $this->getDI();

       $session = $container->getShared("session");

       $tokenKey = $session->get($this->_tokenKeySessionID);

       if (!$tokenKey) {
           return parent::getTokenKey();
       }

       return $tokenKey;
   }
}

services.php

$di->setShared('security', function () {
   return new TokenManager;
});

if i'm using html forms:

<input type="hidden" name="<?= $this->security->getTokenKey() ?>" value="<?= $this->security->getToken() ?>"/>

then in controller

// html form
$this->security->checkToken(null, null, false);

// javascript ( fetch, axios, etc... )
$this->security->checkToken($this->request->getPost('tokenKey'), $this->request->getPost('token'), false);

Reducing the number of files is what I would call a "micro-optimization". Sure, it might cause your site to load a few nanoseconds slower, but the actual effect won't be noticable.

edited Jun '20

well Phalcon's CSFR implementation is that you can assign a new form within the controller and pass it to the view , so when the form is submitted the action on the controller that was assigned by the post url you will need to verify that token and only once per page rerquest since it changes every load.

'Controller'

<?php

namespace Website\Controllers;

use Phalcon\Forms\Form,
    Phalcon\Forms\Element\Hidden;

class IndexController extends ControllerBase
{
  public function IndexAction()
  {
    $form = new Form;

    $element['security'] = new Hidden( "security" ,[
      'name'  => $this->security->getTokenKey(),
      'value' => $this->security->getToken()
    ]);
    foreach ($element as $e)
    {
      $form->add($e);
    }
    $this->view->form    = $form;
  }
}

and then the action to check it :


$this->security->checkToken()  # returns Boolean


2.6k

I understand your opinion very well. Increasing a few read files will not add much delay. But I think that's because of the difference in thinking. It's the same as thinking that a cup with half water is "half full" or "half empty".

And I think the overall speedup is the accumulation of "micro-optimization". Other frameworks need to read themselves, but Phalcon is not needed, right? I feel that the advantage is getting smaller and smaller as the number of adjustment files increases. Maybe I care too much.

There is another point to worry about. You have shown that the time required to read a file is nanoseconds, but the storage devices in the world have not yet been accelerated (not all disks have been replaced by hard disks with SSDs), so reading physical files I think it will take a little more time (at least about microseconds). Of course, depending on the adjustment of op-cache, I think it will be short enough to be ignored as you show.

Reducing the number of files is what I would call a "micro-optimization". Sure, it might cause your site to load a few nanoseconds slower, but the actual effect won't be noticable.



2.6k

Thanks for the reply.

I agree. I understand that CSRF check should not be included in isValid() and should be done independently in the controller. It seems that it is not possible to check it in batch with isValid() by adding validation when defining the element.

well Phalcon's CSFR implementation is that you can assign a new form within the controller and pass it to the view , so when the form is submitted the action on the controller that was assigned by the post url you will need to verify that token and only once per page rerquest since it changes every load.

'Controller'

<?php

namespace Website\Controllers;

use Phalcon\Forms\Form,
   Phalcon\Forms\Element\Hidden;

class IndexController extends ControllerBase
{
 public function IndexAction()
 {
   $form = new Form;

   $element['security'] = new Hidden( "security" ,[
     'name'  => $this->security->getTokenKey(),
     'value' => $this->security->getToken()
   ]);
   foreach ($element as $e)
   {
     $form->add($e);
   }
   $this->view->form    = $form;
 }
}

and then the action to check it :


$this->security->checkToken()  # returns Boolean