CodeIgniter Forums

Full Version: crsf and ion_auth reset password
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I am having a challenge with the csrf token for resetting a password in ion_auth.  I have read other posts here and on other sites about ion_auth & csrf.

In a nutshell, it seems the csrf data stored in flashdata under $_SESSION does not survive from the form created and when the form is submitted.

PHP Code:
    public function reset_password($code NULL)
    {
        if (!
$code)
        {
            
show_404();
        }

        
$user $this->ion_auth->forgotten_password_check($code);

        if (
$user)
        {
            
// if the code is valid then display the password reset form

            
$this->form_validation->set_rules('new'$this->lang->line('reset_password_validation_new_password_label'), 'required|min_length[' $this->config->item('min_password_length''ion_auth') . ']|max_length[' $this->config->item('max_password_length''ion_auth') . ']|matches[new_confirm]');
            
$this->form_validation->set_rules('new_confirm'$this->lang->line('reset_password_validation_new_password_confirm_label'), 'required');

            if (
$this->form_validation->run() === FALSE)
            {
                
// display the form

                // set the flash data error message if there is one
                
$this->data['message'] = (validation_errors()) ? validation_errors() : $this->session->flashdata('message');

                
$this->data['min_password_length'] = $this->config->item('min_password_length''ion_auth');
                
$this->data['new_password'] = array(
                    
'name' => 'new',
                    
'id' => 'new',
                    
'type' => 'password',
                    
'pattern' => '^.{' $this->data['min_password_length'] . '}.*$',
                );
                
$this->data['new_password_confirm'] = array(
                    
'name' => 'new_confirm',
                    
'id' => 'new_confirm',
                    
'type' => 'password',
                    
'pattern' => '^.{' $this->data['min_password_length'] . '}.*$',
                );
                
$this->data['user_id'] = array(
                    
'name' => 'user_id',
                    
'id' => 'user_id',
                    
'type' => 'hidden',
                    
'value' => $user->id,
                );
                
$this->data['csrf'] = $this->_get_csrf_nonce();
                
$this->data['code'] = $code;

                
$this->data['site_title'] = 'Reset Password — '.$this->config->item('site_title');

                
// render
                
$this->_render_page('reset_password_form'$this->data);
            }
            else
            {
                
// do we have a valid request?
                
if ($this->_valid_csrf_nonce() === FALSE || $user->id != $this->input->post('user_id'))
                {

                    
// something fishy might be up
                    
$this->ion_auth->clear_forgotten_password_code($code);

                    
show_error($this->lang->line('error_csrf'));

                }
                else
                {
                    
// finally change the password
                    
$identity $user->{$this->config->item('identity''ion_auth')};

                    
$change $this->ion_auth->reset_password($identity$this->input->post('new'));

                    if (
$change)
                    {
                        
// if the password was successfully changed
                        
$this->session->set_flashdata('message'$this->ion_auth->messages());
                        
redirect("auth/login"'refresh');
                    }
                    else
                    {
                        
$this->session->set_flashdata('message'$this->ion_auth->errors());
                        
redirect('auth/reset_password/' $code'refresh');
                    }
                }
            }
        }
        else
        {
            
// if the code is invalid then send them back to the forgot password page
            
$this->session->set_flashdata('message'$this->ion_auth->errors());
            
redirect("auth/forgot_password"'refresh');
        }
    }

    public function 
_get_csrf_nonce()
    {
        
$this->load->helper('string');
        
$key random_string('alnum'8);
        
$value random_string('alnum'20);
        
$this->session->set_flashdata('csrfkey'$key);
        
$this->session->set_flashdata('csrfvalue'$value);

        return array(
$key => $value);
    }

    public function 
_valid_csrf_nonce()
    {
        
$csrfkey $this->input->post($this->session->flashdata('csrfkey'));
        if (
$csrfkey && $csrfkey === $this->session->flashdata('csrfvalue'))
        {
            return 
TRUE;
        }
        else
        {
            return 
FALSE;
        }
    } 
Stepping through the code with Xdebug, the flashdata of the csrf key and its value is not persisting in $_SESSION between the instant it is set at the time the form is rendered and when the form is submitted.  The flashdata is set by 
Code:
$this->data['csrf'] = $this->_get_csrf_nonce();
when "$this->form_validation->run() === FALSE".  

The steps involved to check the csrf token occur after "// do we have a valid request?".  The function "_valid_csrf_nonce()" returns FALSE for the 'csrfkey' and 'csrfvalue' are no longer present. 

I have tried setting cookies by a file or sessions managed by a database, as recommended on Ben Edmunds' github page.

Has anyone else encountered such a problem and can recommend a solution?  Thanks for taking the time to read this.
Are you using the forms that came in ion auth? I'm still thinking of what is going wrong here but are you sure the problem stems from the controller?
(02-07-2018, 02:02 PM)ChicagoPhil Wrote: [ -> ]Are you using the forms that came in ion auth? I'm still thinking of what is going wrong here but are you sure the problem stems from the controller?

I did not foresee it as an issue but it turns out using forms improved from those provided with the ion_auth package are somehow responsible for the flashdata being deleted from the $_SESSION.

So the question becomes what do I do to make the flashdata persist or what to look for in my forms that is causing the session to regenerate?

I can't identify something in the form to cause the session to regenerate.  All I changed was some of the semantic mark up of html, head and body tags so css or javascript can be incorporated.  The forms in ion_auth are only the form.  No other mark up. I did not change any of the semantics inside the form_open and form_close syntax.

I tried the following syntax to make the session persist. It was inserted at the point form validation is FALSE and the form for submitting a new password is generated (second to last line):

PHP Code:
if ($this->form_validation->run() === FALSE)
{
    
// display the form

    // set the flash data error message if there is one
    
$this->data['message'] = (validation_errors()) ? validation_errors() : $this->session->flashdata('message');

    
$this->data['min_password_length'] = $this->config->item('min_password_length''ion_auth');
    
$this->data['new_password'] = array(
        
'name' => 'new',
        
'id' => 'new',
        
'type' => 'password',
        
'pattern' => '^.{' $this->data['min_password_length'] . '}.*$',
    );
    
$this->data['new_password_confirm'] = array(
        
'name' => 'new_confirm',
        
'id' => 'new_confirm',
        
'type' => 'password',
        
'pattern' => '^.{' $this->data['min_password_length'] . '}.*$',
    );
    
$this->data['user_id'] = array(
        
'name' => 'user_id',
        
'id' => 'user_id',
        
'type' => 'hidden',
        
'value' => $user->id,
    );
    
$this->data['csrf'] = $this->_get_csrf_nonce();
    
$this->data['code'] = $code;

    
$this->data['site_title'] = 'Reset Password — '.$this->config->item('site_title');
    
    
//added to keep flashdata persistent
    
$this->session->keep_flashdata(array('csrfkey','csrfvalue'));

    
// render
    
$this->_render_page('reset_password_form'$this->data); 
This is copied from the user guide. Perhaps your form is missing this post data?

Code:
$csrf = array(
        'name' => $this->security->get_csrf_token_name(),
        'hash' => $this->security->get_csrf_hash()
);

...

<input type="hidden" name="<?=$csrf['name'];?>" value="<?=$csrf['hash'];?>" />
(02-08-2018, 12:43 AM)ChicagoPhil Wrote: [ -> ]This is copied from the user guide. Perhaps your form is missing this post data?

Code:
$csrf = array(
       'name' => $this->security->get_csrf_token_name(),
       'hash' => $this->security->get_csrf_hash()
);

...

<input type="hidden" name="<?=$csrf['name'];?>" value="<?=$csrf['hash'];?>" />

No, this is not the issue of missing data.  The missing token name and token value are present in the DOM.  It is the identical values in the $_SESSION that go *poof* somehow with rendering the view.
I suspect that session and or cookie configs are wrong. Care to share those config settings?
(02-08-2018, 08:11 AM)dave friend Wrote: [ -> ]I suspect that session and or cookie configs are wrong. Care to share those config settings?

Researching this further, there are some threads that mention the csrf programming in ion_auth can be dropped in favour of the csrf protection built-in to CI 3.x.  If I enable csrf protection using settings below I get the following error when I submit a form:

Code:
An Error Was Encountered
The action you have requested is not allowed.
My understanding is all of the checks for csrf protection are done automatically by CI. The form I tested did not use ion_auth csrf protection.

These are the config statements for csrf protection taken from application/config/config.php
PHP Code:
$config['csrf_protection'] = TRUE;
$config['csrf_token_name'] = 'csrf_test_name';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;
$config['csrf_regenerate'] = TRUE;
$config['csrf_exclude_uris'] = array(); 

These are the config statements for sessions taken from application/config/config.php
PHP Code:
$config['sess_driver'] = 'database';
$config['sess_cookie_name'] = 'ci_session';
$config['sess_expiration'] = 7200;
$config['sess_save_path'] = 'ci_sessions';
$config['sess_match_ip'] = FALSE;
$config['sess_time_to_update'] = 300;
$config['sess_regenerate_destroy'] = FALSE

These are the config statements for cookies taken from application/config/config.php
PHP Code:
$config['cookie_prefix']    = '';
$config['cookie_domain']    = '';
$config['cookie_path']        = '/';
$config['cookie_secure']    = FALSE;
$config['cookie_httponly']     = FALSE
The error: "The action you have requested is not allowed." might be because you either
1) Did not use form_open() in your view or
2) Did not add a hidden field to the form with the CSRF token and hash.

form_open() automatically adds the hidden field for you.

Nothing jumps out at me as wrong in your config settings.

I developed a simple controller and view to test if sessions are working.
It's easy to install (and remove) from a project and should provide a definitive answer to the question, "Are sessions working?"
The files are on github HERE

Hope it is helpful.
(02-08-2018, 11:02 AM)dave friend Wrote: [ -> ]The error: "The action you have requested is not allowed." might be because you either
1) Did not use form_open() in your view or
2) Did not add a hidden field to the form with the CSRF token and hash.

form_open() automatically adds the hidden field for you.

Nothing jumps out at me as wrong in your config settings.

I developed a simple controller and view to test if sessions are working.
It's easy to install (and remove) from a project and should provide a definitive answer to the question, "Are sessions working?"
The files are on github HERE

Hope it is helpful.

Thanks for the sessions testing package.  I ran it and according to it, sessions are working.

The csrf protection is not working despite using form_open.  The hidden fields for csrf data are present in the form and the values present in $_POST at the time of form submission.

I am shifting from trying to resolve csrf as implemented by ion_auth to using csrf as implemented by CI 3.x.  I figure better to implement it site-wide and debug the challenges than getting it to work one way and then having to debug the ion_auth methods.

With `$config['csrf_regenerate'] = TRUE;` and using html valid forms, csrf protection fails.  Using the barebones forms supplied with ion_auth, csrf protection passes.  Stepping through function csrf_verify() (line 206 of Security.php in CI ver. 3.1.6) there is a discrepancy between the crsf_hash in the cookie, $this->_csrf_hash and the hash within $_POST.  It is as though somehow html valid forms are submitted twice, the second pass the new value of the csrf hash in $_POST is not updated to the cookie before the form is submitted.

If this convoluted and confusing, I apologise. Trying to get a handle on this is proving a challenge.