Search code examples
phpformstokencsrfcsrf-protection

Unique form token disables multitasking for the user


If I want to protect my site and users from Cross Site Forgery (CSRF) attacks, I can generate a unique token $token = md5( time() * rand ); on every page that has a form. The token is is submitted in a hidden input field echo '<input type="hidden" name="token" value="'.$token.'">'; and at the same time stored in a session variable $_SESSION['token'] = $token;.

I will check if on any submitted form if($_POST['token'] == $_SESSION['token']) and proceed accordingly.

However some users may multitask. Which is something that I am actually doing right now, while I am posting this.

While composing my post I open different windows / tabs to possibly research information or look at some other questions on stack overflow. Stack overflow lets me submit the form with no problems.

But if I were to do that on my site doing this - meaning browse other pages while still composing a post/form - my $token would be regenerated each time I pull up a different page from my website. Making the hidden input token on the form I am working on and eventually want to submit incorrect, because it wont match the $_SESSION['token'] variable anymore, which has been regenerated when I visited a different page...

Any good ideas how to prevent this issue, or any better solutions to stop CSRF in the first place?

I want to allow my users to multi task AND want to be protected against CSRF...


Solution

  • I've had the same problem with what you state because of single CSRF and it gets replaced unless they submit the latest page, but if you use a array w/session it should solve your problem(s). Also you might want to include a captcha, I'd recommend Google's Recaptcha.

    session_start();
    function createToken(){
        $token = sha1(uniqid(mt_rand(), true));
        $_SESSION['Tokens']['Token'][] = $token;
        $_SESSION['Tokens']['Time'][] = time() + (10 * 60); #10 min limit
        #you can omit/change this if you want to not limit or extend time limit
        return $token;
    }
    
    function checkToken($token){
        clearTokens();
        foreach($_SESSION['Tokens']['Token'] as $key => $value){
            if($value === $token){
                return true;
            }
        }
        return false;
    }
    
    function clearTokens(){
        foreach($_SESSION['Tokens']['Time'] as $key => $value){
            if($value <= time()){
                unset($_SESSION['Tokens']['Token'][$key], $_SESSION['Tokens']['Time'][$key]);
                #remove last parameter if you aren't using token time limit
            }
        }
    }
    

    your HTML:

    <input type="hidden" name="token" value="<?php createToken(); ?>">
    

    PHP Token Checker

    if(isset($_POST['token']) && checkToken($_POST['token'])){
        #valid token
    }else{
        #create error message saying that they tried to repost data or session token expired
    }