Why Jquery Ajax get 403 (forbidden) errors with cs

2019-06-14 09:56发布

问题:

I'm developing a registration using jquery and CodeIgniter3.0.0 But after I enable csrf_protection to true my ajax is stop working.But if I disable csrf_protection to False my ajax will work perfect.

Here is my form

<?PHP echo form_open('account#', array('method' => 'POST', 'id' => 'createform')); ?>
<div class="control-group">
    <label class="control-label"  for="lname">Last Name</label>
    <div class="control">
        <?PHP echo form_input('lastname', set_value('lastname', ''), 'id="lastname" class="form-control ln-error" ') ?>
    </div>
</div>
<div class="control-group">
    <label class="control-label"  for="fname">First Name</label>
    <div class="control">
        <?PHP echo form_input('firstname', set_value('firstname', ''), 'id="firstname" class="form-control ln-error" ') ?>
    </div>
</div>
<div class="control-group">
    <label class="control-label"  for="email"> Email <span id="email-error">Email is existed</span></label>
    <div class="control">
        <?PHP echo form_input('email', set_value('email', ''), 'id="email" class="form-control ln-error" placeholder="Example@website.com" ') ?>
    </div>
</div>
<div class="control-group">
    <label class="control-label" >Password</label>
    <div class="control">
        <?PHP echo form_password('pass', set_value('pass', ''), 'id="pass" class="form-control ln-error" ') ?>
    </div>
</div>
<div class="control-group">
    <div class="controls">
        <?PHP echo form_submit('csubmit', "Create Account", 'id="csubmit" class="btn btn-success btn-lg" ') ?>
    </div>
</div>                                    
<?PHP echo form_close(); ?>

After I finish form I used Jquery to validate form field

<strip>
 $(document).already(function(){
       $("#email").keyup(function () {
            if ($("#email").val().length >= 0 || $("#email").val() !== '') {
                ajax_call();
            }
        });

 });

function ajax_call() {
        $.ajax({
            type: "post",
            url: "<?php echo base_url('account/check_user_existing'); ?>",
//            data: $("#createform").serialize(),
            data: {
                '<?php echo $this->security->get_csrf_token_name(); ?>': '<?php echo $this->security->get_csrf_hash(); ?>',
                 email:$("#email").val()
    },
            success: function () {
                alert("success");
            },
            dataType: "text",
            cache: false,
            success: function (respond) {
                if ($.trim(respond) === "true") {
                    $("#email-error").css({'display': 'inline', 'font-size': '12px'});
                    $(".form-error").css({'border': '1px solid red', 'background-color': 'rgba(255, 0, 0, 0.17)', 'color': 'black'});
                    document.getElementById("csubmit").disabled = true;
                } else {
                    $("#email-error").css({'display': 'none'});
                    $(".form-error").css({'border': '', 'background-color': '', 'color': ''});
                    document.getElementById("csubmit").disabled = false;
                }
            }
        });
    }
</strip>

And after I validation by click on the form element I will get error as below image

回答1:

After setting up your code to have a play with, I discovered that the CSRF Token is being regenerated (changed ) on each AJAX Call.

So the first keypress is fine - subsequent ones result in the post being rejected as your CSRF Token is no longer valid.

Quick Nasty Fix: One thing you can do is change

//$config['csrf_regenerate'] = TRUE;
$config['csrf_regenerate'] = FALSE;

Ideally you could leave it set to TRUE and on each post response send back the new Token. I'm working on that.

CSRF Regenerate = true Fix:

Dealing with the case where you want to allow the CSRF token to regenerate on each Post by setting

$config['csrf_regenerate'] = TRUE;

you can do this... NOTE: I have some console.log debug code in this - you can remove it.

<script>    
function ajax_call() {
            var tokenValue = $("input[name='csrf_test_name']");
            console.log('Token is ' + tokenValue.val());
            $.ajax({
                type: "post",
                url: "<?php echo base_url('account/check_user_existing'); ?>",
                data: {
                    '<?php echo $this->security->get_csrf_token_name(); ?>': tokenValue.val(),
                    email:$("#email").val()
                },
                dataType: "json",
                cache: false,
                success: function (data) {
                    console.log('The returned DATA is ' + JSON.stringify(data));
                    console.log('The returned token is ' + data.token);
                    tokenValue.val(data.token);

                    if (data.response == false) {
                        $("#email-error").css({'display': 'inline', 'font-size': '12px'});
                        $(".form-error").css({'border': '1px solid red', 'background-color': 'rgba(255, 0, 0, 0.17)', 'color': 'black'});
                        document.getElementById("csubmit").disabled = true;
                    } else {
                        $("#email-error").css({'display': 'none'});
                        $(".form-error").css({'border': '', 'background-color': '', 'color': ''});
                        document.getElementById("csubmit").disabled = false;
                    }
                }
            });
        }
</script>

Note: The dataType is changed from text to json. Also I have hardcoded the token name which isn't desirable. That can be passed in, in a number of ways ( an exercise for you ).

And the test code for account/check_user_existing

    public function check_user_existing(){
            $new_token = $this->security->get_csrf_hash();
            $response = FALSE;
            if($this->input->post('email') == 'testmy@email.address')
            {
                $response = TRUE;
            }
            echo json_encode(array('response'=>$response,'token'=>$new_token));
            exit();
     }

For the sake of regenerating the CSRF Token on each Post on every Keyup event might be a little overboard. But that's another issue.