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 Protection

Hi all! Who knows how can I protect all forms (~10 forms) on page of CSRF? I already tried to set hidden input:

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

in form builder class:

// CSRF
$csrf = new Hidden('csrf');
$csrf->addValidator(
    new Identical(array(
        'value' => $this->security->getSessionToken(),
        'message' => $t->form->post->csrf
    ))
);

But it's always validating as false. I have 3 suggestions:

  1. I should start session in some BaseController, but I don't know how to do it.. Help plz!
  2. Every form have an unique value="...", but server side stores only last token... So on one page all forms protected from CSRF will fails except of last form. (I hope that's not true.)

Any suggestions?

Hi! Thanks for your answer, have one more question can I use all of this but without form builder??

Huh! That's not so obivous, that what I need this line:

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

to validate the token, even route to action is already declared as addPost(...) ;)

edited Oct '14

PFFFFFFFFFFFFF I need your help again! I did exaclty the same like in that chapter. I developing app like a forum, on one page I have a 10 themes on page and under each theme I have a form called "Quick post" each of this form protected from CSRF. And when I testing it, answering in many themes on one page, my token is invalidates very often.

Action that handles adding post request

public function addAction($boardAbbr)
    {
        if ($this->request->isPost()) {
            if ($this->security->checkToken()) {

              ..................

               if (!$post->save()) {
                    foreach ($post->getMessages() as $message)
                    $this->flash->error((string) $message);

                } else {
                    if ($this->request->isAjax()) {
                        echo json_encode(array(
                            'status' => true,
                            'message' => 'post added.',
                            'data' => array(
                                'post_id' => $post->id,
                                'pictures' => explode('|', $names)
                            )
                        ));
                    }
                    else {
                        $this->flashSession->success('post added.');
                        $this->response->redirect('#post-'.$post->id);
                    }
                }
            } else { echo 'token is bad.'; }

In form:

<input type="hidden" name="{{ security.getTokenKey() }}" value="{{ security.getToken() }}">

Token invalidates from time to time ..... What can I do??

Get the CSRF tokens in the controller through the Security and assign it to the view.

<?php
class AccountController extends BaseController
{
    public function indexAction()
    {
        $this->view->setVars([
            'tokenKey' => $this->security->getTokenKey(),
            'token' => $this->security->getToken()
        ]);
    }
}

Then My view I re-used the variable:

<form method="post" action="{{ url('account/doEmailUpdate') }}">
    <input type="text" name="email" class="form-control" placeholder="Email address" autofocus>
    <input type="text" name="confirm_email" class="form-control" placeholder="Confirm">
    <input type="hidden" name="{{ tokenKey }}" value="{{ token }}" />
    <input class="btn btn-lg btn-primary btn-block" type="submit" value="Submit">
</form>
<form method="post" action="{{ url('account/doPasswordUpdate') }}">
    <input type="text" name="password" class="form-control" placeholder="New Password">
    <input type="text" name="confirm_password" class="form-control" placeholder="Confirm">
    <input type="hidden" name="{{ tokenKey }}" value="{{ token }}" />
    <input class="btn btn-lg btn-primary btn-block" type="submit" value="Submit">
</form>


4.2k

Thank you for this advice. Actually, to manage token for form security, I found 2 things important:

  • First One: if you want to add a form using getToken() from IndexController, you will have a problem as getToken() will be called each time, so that the csrf checking will fail. My solution; create a HomeController and setup default controller at home

  • Second One: if you have more than one form for same action/controller, if each form call getToken(), you will have 2 different tokens. So the last form will work but not the first one Jesse solution solve this problem.

I learn Phalcon/csrf with Vokuro sample and I'm not sure this the best way as it's not dealing with form in index page or multi-form on same page

Sorry for my english, I hope that'll help

I know the problem is solved but there is an easier way too,

if you see checkToken method defenition:

public boolean checkToken ([string $tokenKey], [string $tokenValue])

it gets input parameters so you just need to define unique key for each form and when you want to check token, pass both key and the value to checkToken method.

For pages that have more than one form you can use the same token for all forms. You are protecting the page and all form(s) from cross site attacks. Protecting multiple forms individually has no advantage.

Thank you for this advice. Actually, to manage token for form security, I found 2 things important:

  • First One: if you want to add a form using getToken() from IndexController, you will have a problem as getToken() will be called each time, so that the csrf checking will fail. My solution; create a HomeController and setup default controller at home

  • Second One: if you have more than one form for same action/controller, if each form call getToken(), you will have 2 different tokens. So the last form will work but not the first one Jesse solution solve this problem.

I learn Phalcon/csrf with Vokuro sample and I'm not sure this the best way as it's not dealing with form in index page or multi-form on same page

Sorry for my english, I hope that'll help



13.8k

I had a problem getting the check for the token to work on a login form and i could solve it using your method.


$token    = $this->request->getPost('csrf');

if($this->security->checkToken( $token, $this->security->getSessionToken()) ) {

}

I think it is worth noting that the code below:

    $security = new Hidden('security');
    $security->addValidators([
        new Identical([
            'value' => $this->security->getSessionToken()
        ])
    ]);
    $this->add($security);

Can be classed as insecure... the secure way would be:

    $security = new Hidden('security');
    $security->addValidators([
        new PresenceOf([
        ]),
        new Identical([
            'value' => $this->security->getSessionToken()
        ])
    ]);
    $this->add($security);

If the client does not have cookies enabled, then the session token would be null and that means the csrf input can be removed and the form would submit.



1.3k

This is the best solution IMO.

Get the CSRF tokens in the controller through the Security and assign it to the view.

<?php
class AccountController extends BaseController
{
   public function indexAction()
   {
       $this->view->setVars([
           'tokenKey' => $this->security->getTokenKey(),
           'token' => $this->security->getToken()
       ]);
   }
}

Then My view I re-used the variable:

<form method="post" action="{{ url('account/doEmailUpdate') }}">
   <input type="text" name="email" class="form-control" placeholder="Email address" autofocus>
   <input type="text" name="confirm_email" class="form-control" placeholder="Confirm">
   <input type="hidden" name="{{ tokenKey }}" value="{{ token }}" />
   <input class="btn btn-lg btn-primary btn-block" type="submit" value="Submit">
</form>
<form method="post" action="{{ url('account/doPasswordUpdate') }}">
   <input type="text" name="password" class="form-control" placeholder="New Password">
   <input type="text" name="confirm_password" class="form-control" placeholder="Confirm">
   <input type="hidden" name="{{ tokenKey }}" value="{{ token }}" />
   <input class="btn btn-lg btn-primary btn-block" type="submit" value="Submit">
</form>


1.3k

Need to point out that the answer above has a major issue with multiple windows / tabs.

I also found out the hard way that getSessionToken() is closely related to getToken(). It likes to become null unless getToken() is called on every form.

The only solution is to store the token in session. This is the only right solution that supports multiple windows: https://forum.phalcon.io/discussion/18827/csrf-security

I developing app like a discussion board, on one web page I even have a ten issues on web page and below each subject I actually have a form called "Quick submit" every of this shape blanketed from CSRF.