I'm using Codeigniter 3.1.3 and having CSRF protection enabled in config file. Somehow I'm always getting 500 (Internal server error) from the ajax $.post if the form passes the validation. I'm not getting that error if the validation fails. Any thoughts?
Here is the csrf setting in codeigniter config file:
$config['csrf_protection'] = TRUE;
$config['csrf_token_name'] = 'csrf_token_name';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;
$config['csrf_regenerate'] = TRUE;
Here is my login form within the view login.php:
<form id="login_form" class="col-12 col-md-8 mx-auto" method="post">
<div class="alert" id="login-alert" role="alert"></div>
<div class="form-group input-group">
<span class="input-group-addon" id="basic-addon1">Username</span>
<input type="text" class="form-control" id="username" name="username" />
</div>
<div class="form-group input-group">
<span class="input-group-addon" id="basic-addon2">Password</span>
<input type="password" class="form-control" id="pwd" name="pwd" />
</div>
<div class="text-center">
<button type="button" id="login_btn" class="btn btn-primary">LOGIN</button>
</div>
</form>
I'm using AJAX $.post to submit the form
$("#login_btn").click(function(){
var data = $("#login_form").serialize();
var csrf_name = "<?=$this->security->get_csrf_token_name()?>";
//get_cookie is a function that I defined to retrieve the cookie
var csrf_cookie = get_cookie("csrf_cookie_name");
$.post('index.php/sentinel/verify_user',data+"&"+csrf_name+"="+csrf_cookie,function(data){
if(data && data !== '')
{
data = data.replace(/(<p>)/g,'').replace(/(<\/p>)/g,'<br>');
$("#login-alert").prop('class','alert alert-danger').html(data);
}
else
{
//success
//redirect to the main page
}
});
});
function get_cookie( check_name ) {
var a_all_cookies = document.cookie.split( ';' );
var a_temp_cookie = '';
var cookie_name = '';
var cookie_value = '';
var b_cookie_found = false; // set boolean t/f default f
for ( i = 0; i < a_all_cookies.length; i++ )
{
// now we'll split apart each name=value pair
a_temp_cookie = a_all_cookies[i].split( '=' );
// and trim left/right whitespace while we're at it
cookie_name = a_temp_cookie[0].replace(/^\s+|\s+$/g, '');
// if the extracted name matches passed check_name
if ( cookie_name == check_name )
{
b_cookie_found = true;
// we need to handle case where cookie has no value but exists (no = sign, that is):
if ( a_temp_cookie.length > 1 )
{
cookie_value = unescape( a_temp_cookie[1].replace(/^\s+|\s+$/g, '') );
}
// note that in cases where cookie is initialized but no value, null is returned
return cookie_value;
break;
}
a_temp_cookie = null;
cookie_name = '';
}
if ( !b_cookie_found )
{
return null;
}
}
Here is my controller:
public function verify_user(){
$this->form_validation->set_rules('username', 'Username', 'required|alpha');
$this->form_validation->set_rules('pwd', 'Password', 'required|callback_alpha_numeric_dots');
$this->form_validation->set_message('alpha_numeric_dots','Invalid Password.');
if ($this->form_validation->run() == FALSE)
{
echo validation_errors();
}
else
{
$this->form_validation->set_rules('pwd','Password','callback_login_check');
if ($this->form_validation->run() == FALSE)
{
echo validation_errors();
}
else
{
echo '';
}
}
}
public function login_check(){
$data = $this->security->xss_clean($this->input->post());
$rep_info= $this->sentinel_model->user_verify($data);
if($rep_info === FALSE)
{
$this->form_validation->set_message('login_check', 'Incorrect Username or Password');
return FALSE;
}
else
{
//set session data here
$newdata = array(
'id' =>$this->encryption->encrypt($rep_info['user_id']),
'name' => $rep_info['user_name'],
'email' => $rep_info['user_email'],
'logged_in' => TRUE
);
$this->session->set_userdata($newdata);
return TRUE;
}
}
public function alpha_numeric_dots($str)
{
return (bool) preg_match('/^[A-Z0-9.]+$/i', $str);
}
I'd also like to redirect users to another view without changing the url after they logged in successfully. What is the best way to do it?
The CSRF
token is added to the form as a hidden input when the form_open()
function is used and its quick fix.
A cookie with the CSRF
token's value is created by the Security
class, and regenerated if necessary for each request.
If $_POST
data exists, the cookie is automatically validated by the Input class. If the posted token does not match the cookie's value, CI
will show an error and fail to process the $_POST
data.
So basically, it's all automatic - all you have to do is enable it in your $config['csrf_protection']
and use the form_open()
function for your form.
So client side you just have to post
$.post('index.php/sentinel/verify_user',$("#login_form").serialize(),function(data){
});
In your controller
$this->load->helper('form');
and in you view file
<?php echo form_open('sentinel/verify_user', 'id="login_form" class="col-12 col-md-8 mx-auto"'); ?>
<div class="alert" id="login-alert" role="alert"></div>
<div class="form-group input-group">
<span class="input-group-addon" id="basic-addon1">Username</span>
<input type="text" class="form-control" id="username" name="username" />
</div>
<div class="form-group input-group">
<span class="input-group-addon" id="basic-addon2">Password</span>
<input type="password" class="form-control" id="pwd" name="pwd" />
</div>
<div class="text-center">
<button type="button" id="login_btn" class="btn btn-primary">LOGIN</button>
</div>
</form>
OR
create hidden input inside form like below
<input type="hidden" name="<?php echo $this->security->get_csrf_token_name(); ?>" value="<?php echo $this->security->get_csrf_hash();?>" />
OR
without modifying view file, (corrected version of what you have posted )
var data = $("#login_form").serialize();
$.post(
'index.php/sentinel/verify_user',
data+"&" + '<?php echo $this->security->get_csrf_token_name(); ?>' +"="+ '<?php echo $this->security->get_csrf_hash(); ?>',
function(data){
});
For comment
Actually I just noticed that I'm getting 403(Fordidden) error instead of 500 once i click the login button the 2nd time
From the docs:
Tokens may be either regenerated on every submission (default) or kept the same throughout the life of the CSRF cookie. The default regeneration of tokens provides stricter security, but may result in usability concerns as other tokens become invalid (back/forward navigation, multiple tabs/windows, asynchronous actions, etc). You may alter this behavior by editing the following config parameter
$config['csrf_regenerate'] = TRUE;
Set that to FALSE.