I have a codeigniter4 application.
When i set public $regenerate = true;
in Security.php
the view with a form submit to add employee redirects to itself on submit. But when regenerate is false, code submits to the controller and data is processed as required
How can i solve the issue with regenerate set to true?.
The controller function handling add_employee is:
public function add_employee()
{
$session = session();
$request = ConfigServices::request();
$db = ConfigDatabase::connect();
$usermodel = new AppModelsUserModel();
$data['user']=$usermodel->getRow($session->get('email'));
$data['title'] = 'Add employee';
$employeemodel = new AppModelsEmployeeModel();
$divisionmodel = new AppModelsDivisionModel();
$userRoleModel = new AppModelsUserRoleModel();
$data['employee'] = $employeemodel->getAll();
$data['designation'] = $employeemodel->getAllDesignations();
$data['division'] = $divisionmodel->findAll();
$data['roles'] = $userRoleModel->findAll();
$role_id= $data['user']['role_id'];
if($role_id==5||$role_id==10){ //Division Admin or Division head
$builder=$db->table('tbl_division');
$builder->select('tbl_division.int_divisionID');
if($role_id==5){
$builder->join('tbl_administrator','tbl_administrator.int_divisionID=tbl_division.int_divisionID');
} else{
$builder->join('tbl_employee','tbl_employee.int_divisionID=tbl_division.int_divisionID');
}
$division=$builder->get()->getRowArray();
$data['selected_division'] = $division['int_divisionID'];
$builder=$db->table('tbl_department');
$builder->select('tbl_department.int_departmentID,tbl_department.vchr_departmentName');
$builder->where('tbl_department.int_divisionID',$division['int_divisionID']);
$data['department']= $builder->get()->getResultArray();
}
elseif($role_id==11)//Department Head
{
$builder=$db->table('tbl_employee');
$builder->select('int_departmentID');
$builder->where('tbl_employee.int_userID',$data['user']['id']);
$department = $builder->get()->getRowArray();
$department_id = $department['int_departmentID'];
$data['department_id']=$department_id;
$builder=$db->table('tbl_department');
$builder->select('tbl_department.int_divisionID');
$builder->where('tbl_department.int_departmentID',$department_id);
$division=$builder->get()->getRowArray();
$data['selected_division'] = $division['int_divisionID'];
$builder=$db->table('tbl_department');
$builder->select('tbl_department.id,tbl_department.int_departmentID,tbl_department.vchr_departmentName');
$builder->where('tbl_department.int_divisionID',$division['int_divisionID']);
$data['department']= $builder->get()->getResultArray();
}
if ($this->request->getMethod() == "post") {
$validation = ConfigServices::validation();
$_POST['csrf_test_name']=$_REQUEST['csrf_test_name'];
$rules = [
"csrf_test_name" => [
"label" => "CSRF Token Name",
"rules" => "required|trim|alpha_numeric|max_length[64]"
],
"vchr_CDIT_employeeID" => [
"label" => "CDIT EmployeeID",
"rules" => "required|trim|alpha_numeric|max_length[10]"
],
"vchr_employeeName" => [
"label" => "Employee Name",
"rules" => "required|trim|alpha_space|max_length[100]"
],
"vchr_email_id" => [
"label" => "Email",
"rules" => "required|trim|valid_email|max_length[100]"
],
"int_mobileNo" => [
"label" => "Mobile No",
"rules" => "required|trim|numeric|max_length[10]|min_length[10]"
],
"int_empType" => [
"label" => "Mobile No",
"rules" => "required|trim|numeric|max_length[3]"
]
];
$int_empType = $request->getPost('int_empType');
if(!($int_empType==7 || $int_empType==8)){
$rules1= [
"int_designationID" => [
"label" => "Designation",
"rules" => "required|trim|numeric|max_length[4]"
],
"int_divisionID" => [
"label" => "Division",
"rules" => "required|trim|numeric|max_length[4]"
]
];
$rules=array_merge($rules,$rules1);
}
if(!($int_empType==7 || $int_empType==8 || $int_empType==9 )){
$rules1= [
"int_departmentID" => [
"label" => "Department",
"rules" => "required|trim|numeric|max_length[4]"
]
];
$rules=array_merge($rules,$rules1);
}
if (!($this->validate($rules))) {
$session->setFlashdata('employeemessage', '<div class="alert alert-danger" role="alert">
Validation Errors!</div>');
return redirect()->back()->withInput();
}
else{
$email = strip_tags($request->getPost('vchr_email_id'));
$name = strip_tags($request->getPost('vchr_employeeName'));
$employeeemailexists = $employeemodel->where('vchr_email_id',$email)->first();
if($employeeemailexists){
$session->setFlashdata('employeemessage', '<div class="alert alert-danger" role="alert">
employee Creation Failed. employee with this email already exists</div>');
return redirect()->to('employee');
}
$userdata = $usermodel->where('email',$email)->first();
if($userdata){
$session->setFlashdata('employeemessage', '<div class="alert alert-danger" role="alert">
User Creation Failed. User with this email already exists</div>');
return redirect()->to('employee');
}
$date_created = date("Y-m-d H:i:s", time());
$comb = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*_-";
$shfl = str_shuffle($comb);
$pwd = substr($shfl,0,12);
$data = [
'name' => $name,
'email' => $email,
'image' => 'default.jpg',
'password' => md5($pwd),
'role_id' => 4,
'is_active' => 1,
'isFirstLogin' => 1,
'date_created' => $date_created
];
$token = base64_encode(random_bytes(32));
$emailstatus=sendemail($name,$email,$token,$pwd, 'verify');
if(!$emailstatus){
$emailmsg='<span class="alert alert-danger" role="alert">Password couldnot be send through email.Check mail configuration</span>';
}
else
$emailmsg='<span class="alert alert-success" role="alert"> Password send to employee email </span>';
$usermodel->insert($data);
$res=$usermodel->where('email',$email)->first();
$insert_id=$res['id'];
$employeemodel->saveToDB($insert_id);
$session->setFlashdata('employeemessage', '<div class="alert alert-success" role="alert">
Employee added successfully!.Password is <b>'.$pwd.'</b></div>'.$emailmsg);
return redirect()->to('employee');
}
}
else{
echo view('templates/admin_header', $data).
view('templates/admin_sidebar').
view('templates/admin_topbar', $data).
view('employee/add_employee').
view('templates/admin_footer');
}
}
My view add_employee
<!-- Begin Page Content -->
<div class="container-fluid">
<!-- Page Heading -->
<h1 class="h3 mb-4 text-gray-800">Add Employees </h1>
<div class="card shadow mb-3">
<!--<div class="card-header">
Add Employees
</div>-->
<div class="card-body">
<div style="display: flex; justify-content: flex-end"> <button onclick="history.back();"><i class="fas fa-arrow-left"></i> Back</button>
</div>
<?php
$attributes = array('id' => 'myform');
echo form_open('employee/add_employee',$attributes);?>
<div class="form-group">
<label for="vchr_CDIT_employeeID">CDIT Employee ID<font color="red"> *</font></label>
<input class="form-control" maxlength="100"
type="text" name="vchr_CDIT_employeeID" placeholder="Enter CDIT Employee ID"
value="<?= set_value('vchr_CDIT_employeeID'); ?>">
<?= form_error('vchr_CDIT_employeeID', '<small class="text-danger pl-3">', '</small>'); ?>
</div>
<div class="error" width="100%"></div>
<div class="form-group">
<label for="vchr_employeeName">Employee Name<font color="red"> *</font></label>
<input class="form-control" maxlength="100"
type="text" name="vchr_employeeName" placeholder="Enter Employee Name" value="<?= set_value('vchr_employeeName'); ?>">
<?= form_error('vchr_employeeName', '<small class="text-danger pl-3">', '</small>'); ?>
</div>
<div class="error" width="100%"></div>
<div class="form-group">
<label for="vchr_email_id">E-Mail ID<font color="red"> *</font></label>
<input class="form-control" maxlength="100"
type="email" name="vchr_email_id" placeholder="Enter Email ID" value="<?= set_value('vchr_email_id'); ?>">
<?= form_error('vchr_email_id', '<small class="text-danger pl-3">', '</small>'); ?>
</div>
<div class="error" width="100%"></div>
<div class="form-group">
<label for="int_mobileNo">Mobile Number<font color="red"> *</font></label>
<input class="form-control"
type="text" minlength="10" maxlength="10" name="int_mobileNo" placeholder="Enter Mobile Number" value="<?= set_value('int_mobileNo'); ?>">
<?= form_error('int_mobileNo', '<small class="text-danger pl-3">', '</small>'); ?>
</div>
<div class="error" width="100%"></div>
<div class="form-group">
<label for="int_empType">Enter Employee Type <font color="red"> *</font></label>
<select name="int_empType" id="int_empType" class="form-control">
<option value="">---Select Role---</option>
<?php foreach ($roles as $r) : ?>
<?php
if(!in_array($r['id'],[1,3,5]))
{
?><option value="<?= $r['id']; ?>"><?= $r['role']; ?></option>
<?php } endforeach; ?>
</select>
<?= form_error('int_empType', '<small class="text-danger pl-3">', '</small>'); ?>
</div>
<div class="error" width="100%"></div>
<div class="form-group" id="divisionDiv">
<label for="int_divisionID">Enter Division <font color="red"> *</font></label>
<select name="int_divisionID" id="int_divisionID" class="form-control"
>
<?php if(isset($selected_division)) {
foreach ($division as $d) :
if($d['int_divisionID']==$selected_division){
?>
>
<option value="<?= $d['int_divisionID']; ?>" selected > <?= $d['vchr_divisionName']; ?></option>
<?php
} endforeach;
} else {
?>
<option value="">---Select Division---</option>
<?php foreach ($division as $d) : ?>
<option value="<?= $d['int_divisionID']; ?>"
>
<?= $d['vchr_divisionName']; ?></option>
<?php endforeach; }?>
</select>
<?= form_error('int_divisionID', '<small class="text-danger pl-3">', '</small>'); ?>
</div>
<div class="error" width="100%"></div>
<div class="form-group" id="deptdiv">
<label for="int_departmentID">Enter Department <font color="red"> *</font></label>
<select name="int_departmentID" id="int_departmentID" class="form-control">
<?php if(isset($department_id)) {
foreach ($department as $d) :
if($d['int_departmentID']==$department_id){
?>
<option value="<?= $d['int_departmentID']; ?>" selected > <?= $d['vchr_departmentName']; ?></option>
<?php
} endforeach;
} else {
?>
<option value="" selected>---Select Department---</option>
<?php if(isset($department)){
foreach ($department as $d) : ?>
<option value="<?= $d['int_departmentID']; ?>">
<?= $d['vchr_departmentName']; ?></option>
<?php endforeach; }
}?>
</select>
<?= form_error('int_departmentID', '<small class="text-danger pl-3">', '</small>'); ?>
</div>
<div class="error" width="100%"></div>
<div class="form-group" id="desigDiv">
<label for="int_designationID">Enter Designation <font color="red"> *</font></label>
<select name="int_designationID" id="int_designationID" class="form-control">
<option value="">---Select Designation---</option>
</select>
<?= form_error('int_designationID', '<small class="text-danger pl-3">', '</small>'); ?>
</div>
<div class="error" width="100%"></div>
<!-- button save -->
<div class="form-group">
<button type="submit" class="btn btn-success" >Add Employee!</button>
</div>
</form>
</div>
<div class="card-footer small text-muted">
<font color="red"> *</font> must be filled
</div>
</div>
</div>
<!-- /.container-fluid -->
</div>
<!-- End of Main Content -->
</div>
<!-- /.container-fluid -->
</div>
<!-- End of Main Content -->
<script type="text/javascript">
$('#int_divisionID').on('change', function(){
var int_divisionID = $('#int_divisionID').val();
var csrfName = "csrf_test_name";
var csrfHash = $('[name="csrf_test_name"]').val();
$.ajax({
type:"POST",
url: "<?php echo site_url('Department/getDepartments')?>",
data: {[csrfName]: csrfHash, int_divisionID: int_divisionID},
success:function(response){
$('#int_departmentID').html('<option value=""> ---Select Department--- </option>');
$.each(JSON.parse(response), function(key,val) {
$('#int_departmentID').append('<option value="' + val.id + '">'+ val.vchr_departmentName +'</option>');
});
}
});
});
$('#int_departmentID').on('change', function(){
var int_divisionID = $('#int_divisionID').val();
var csrfName = "csrf_test_name";
var csrfHash = $('[name="csrf_test_name"]').val();
$.ajax({
type:"POST",
url: "<?php echo site_url('Department/getDesignations')?>",
data: {[csrfName]: csrfHash, int_divisionID: int_divisionID},
success:function(response){
$('#int_designationID').html('<option value=""> --Select Designation-- </option>');
$.each(JSON.parse(response), function(key,val) {
$('#int_designationID').append('<option value="' + val.id + '">'+ val.vchr_designation +'</option>');
});
}
});
});
</script>
<script type="text/javascript">
$(document).ready(function() {
$('#int_empType').on('change', function(){
var int_empType = $('#int_empType').val();
if(int_empType==7 || int_empType==8 || int_empType==9 ){
$('#deptdiv').hide();
$('#int_departmentID').val(0);
}
else{
$('#deptdiv').show();
$('#divisionDiv').show();
$('#desigDiv').show();
}
if(int_empType==7 || int_empType==8){
$('#divisionDiv').hide();
$('#desigDiv').hide();
$('#int_divisionID').val(0);
}
else{
$('#divisionDiv').show();
}
});
var int_divisionID = $('#int_divisionID').val();
var csrfName = "csrf_test_name";
var csrfHash = $('[name="csrf_test_name"]').val();
$.ajax({
type:"POST",
url: "<?php echo site_url('Department/getDesignations')?>",
data: {[csrfName]: csrfHash, int_divisionID: int_divisionID},
success:function(response){
$('#int_designationID').html('<option value=""> --Select Designation-- </option>');
$.each(JSON.parse(response), function(key,val) {
$('#int_designationID').append('<option value="' + val.id + '">'+ val.vchr_designation +'</option>');
});
}
});
jQuery.validator.addMethod("name_check", function(value, element) {
return this.optional(element) || /^[a-zA-Zs.-]+$/.test(value);
});
jQuery.validator.addMethod("CDIT_employeeID", function(value, element) {
return this.optional(element) || /^[a-zA-Z0-9s.-]+$/.test(value);
});
jQuery.validator.addMethod("email_check", function(value, element) {
return this.optional(element) || /^[a-zA-Z0-9._-]+@([a-zA-Z0-9-]+.[a-zA-Z])*[a-zA-Z0-9-]+.[a-zA-Z]{2,5}$/i.test(value);
});
jQuery.validator.addMethod("phonenumber", function(value, element) {
return this.optional(element) || /^d{10}$/.test(value);
});
$("#myform").validate({
rules: {
vchr_CDIT_employeeID: {
required: true,
CDIT_employeeID:true,
},
vchr_employeeName: {
required: true,
name_check:true,
},
vchr_email_id: {
required: true,
//email_check:true,
},
int_mobileNo: {
required:true,
minlength:10,
maxlength:10,
phonenumber:true,
},
int_divisionID:{
required: {
depends: function(element) {
return (!($('#int_empType').val()==7 || $('#int_empType').val()==8))
}
}
},
int_departmentID :{
required: {depends: function(element) {
return (!($('#int_empType').val()==7 || $('#int_empType').val()==8 || $('#int_empType').val()==9))
}}
},
int_designationID: {
required: {depends: function(element) {
return (!($('#int_empType').val()==7 || $('#int_empType').val()==8 || $('#int_empType').val()==9))
} }
},
int_empType: {
required:true,
},
},
messages: {
vchr_employeeName: {
required: "<font color='#FF0000'> Employee Name required !!</font>",
name_check:"<font color='#FF0000' > Invalid Special characters in Employee Name!!</font>"
},
vchr_email_id: {
required: "<font color='#FF0000'> E-mail ID is required !!</font>",
//email_check: "<font color='#FF0000'> Invalid Email Format !!</font>",
},
int_mobileNo: {
required: "<font color='#FF0000'> Mobile Number is required !!</font>",
minlength : "<font color='#FF0000'> Mobile Number should be of 10 characters long !!</font>",
maxlength : "<font color='#FF0000'> Mobile Number should be of 10 characters long !!</font>",
phonenumber : "<font color='#FF0000'> Mobile Number is Invalid !!</font>",
},
vchr_CDIT_employeeID :{
required: "<font color='#FF0000'> CDIT Employee ID required !!</font>",
CDIT_employeeID : "<font color='#FF0000'> Invalid Employee ID !!</font>",
},
int_divisionID :{
required: "<font color='#FF0000'> Division required !!</font>",
},
int_departmentID :{
required: "<font color='#FF0000'> Department required !!</font>",
},
int_designationID :{
required: "<font color='#FF0000'> Designation required !!</font>",
},
int_empType : {
required: "<font color='#FF0000'> Employee Type required !!</font>",
},
},
errorElement: "span",
errorPlacement: function(error, element) {
error.appendTo(element.parent("div").next());
},
submitHandler: function (form) {
/*var key = "eTicketingCI4CDIT#$abc";
var form = document.getElementById("myForm");
var inputs = form.getElementsByTagName("input");
for (var i = 0; i < inputs.length; i++) {
var input = inputs[i];
if (input.type !== "submit" && input.type !== "button") {
var encryptedValue = CryptoJS.AES.encrypt(input.value, key).toString();
input.value = encryptedValue;
}
}*/
form.submit();
}
});
});
</script>
The CI4 version is 4.2.12.
My Security.php file:
<?php
namespace Config;
use CodeIgniterConfigBaseConfig;
class Security extends BaseConfig
{
/**
* --------------------------------------------------------------------------
* CSRF Protection Method
* --------------------------------------------------------------------------
*
* Protection Method for Cross Site Request Forgery protection.
*
* @var string 'cookie' or 'session'
*/
public $csrfProtection = 'cookie';
/**
* --------------------------------------------------------------------------
* CSRF Token Randomization
* --------------------------------------------------------------------------
*
* Randomize the CSRF Token for added security.
*
* @var bool
*/
public $tokenRandomize = true;
/**
* --------------------------------------------------------------------------
* CSRF Token Name
* --------------------------------------------------------------------------
*
* Token name for Cross Site Request Forgery protection.
*
* @var string
*/
public $tokenName = 'csrf_test_name';
/**
* --------------------------------------------------------------------------
* CSRF Header Name
* --------------------------------------------------------------------------
*
* Header name for Cross Site Request Forgery protection.
*
* @var string
*/
public $headerName = 'X-CSRF-TOKEN';
/**
* --------------------------------------------------------------------------
* CSRF Cookie Name
* --------------------------------------------------------------------------
*
* Cookie name for Cross Site Request Forgery protection.
*
* @var string
*/
public $cookieName = 'csrf_cookie_name';
/**
* --------------------------------------------------------------------------
* CSRF Expires
* --------------------------------------------------------------------------
*
* Expiration time for Cross Site Request Forgery protection cookie.
*
* Defaults to two hours (in seconds).
*
* @var int
*/
public $expires = 7200;
/**
* --------------------------------------------------------------------------
* CSRF Regenerate
* --------------------------------------------------------------------------
*
* Regenerate CSRF Token on every submission.
*
* @var bool
*/
public $regenerate = true;
/**
* --------------------------------------------------------------------------
* CSRF Redirect
* --------------------------------------------------------------------------
*
* Redirect to previous page with error on failure.
*
* @var bool
*/
public $redirect = true;
/**
* --------------------------------------------------------------------------
* CSRF SameSite
* --------------------------------------------------------------------------
*
* Setting for CSRF SameSite cookie token.
*
* Allowed values are: None - Lax - Strict - ''.
*
* Defaults to `Lax` as recommended in this link:
*
* @see https://portswigger.net/web-security/csrf/samesite-cookies
*
* @var string
*
* @deprecated `ConfigCookie` $samesite property is used.
*/
public $samesite = 'Lax';
}
2
Answers
Update the csrftoken in the ajax call in onload of Department/getDesignations.
inside Department/getDesignations I changed the data by added a csrftoken and designation data separately.
At the ajax response side i added the following also
OP statement 1
Explanation 1
This behavior is expected when you set the
$redirect
Security property totrue
.Redirection on Failure
OP statement 2
Explanation 2
It fails because when the
$regenerate
Security property is set totrue
, the CSRF Token is regenerated on everyPOST
,PUT
,DELETE
, andPATCH
submission.Token Regeneration
Solution
With that out of the way, this means you will have to update the next submitted CSRF Token on your client (AJAX) on every submission. To manage that, you may send the latest CSRF Token through a Response Header.
STEP 1: Configure your Security settings in your
.env
file which resides at the root of your project.STEP 2: Set an HTML DOM
<meta />
element to always hold the latest generated CSRF Token. This may be preferably set in your main HTML template.STEP 3: Set up a Filter to manage the need for sending the CSRF Token through a Response Header.
app/Config/Filters.php
app/Filters/CsrfToken.php
STEP 4: On your front-end, set up global jQuery AJAX events to automatically handle the sending and updating of the CSRF Token for all CSRF-protected HTTP methods.
Conclusion: With the above setup, you no longer have to manually send a CRSF Token in your AJAX
POST
request body or pollute your Controller method’s response data. The latest Token will automatically be sent on your behalf from STEP 4.