Crazy Horse
asked on
CSRF form validation fires even when form token matches session variable token - MVC
I have a function which generates a value for the form token. That is in a hidden field in the view:
In my controller I have:
if I echo out the session variable and view the page source, I can see that the hidden variable value for token is exactly the same as the session variable value. Why then do I keep getting my error message that says the CSRF token is invalid?
The strange thing is that I do exactly the same thing in another area of my website and it works just fine. Very confused...
I also know that the session is set because if I change the validation to just:
then the error goes away. So, it is something to do with the hidden field form token.
<form action="<?php echo URLROOT; ?>/users/" method="post">
<input class="form-email" type="email" placeholder="Email Address" name="email" autocomplete="off" value="<?php echo $data['email']; ?>">
<input class="form-password" type="password" placeholder="Password" name="password">
<input class="login-btn btn-filled" id="login" type="submit" value="Login">
<input type="hidden" name="form_token" id="form_token" value="<?php echo make_form_token(); ?>">
</form>
In my controller I have:
public function index()
{
if($_SERVER['REQUEST_METHOD'] == 'POST') {
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
$password = trim($_POST['password']);
$data = [
'email' => $email,
'password' => $password,
'message' => ''
];
if(!isset($_SESSION['token']) || $_POST['form_token'] !== $_SESSION['token']) {
$data['message'] .= "CSRF token invalid <br />";
}
// other form validation here
if(!empty($data['message'])) {
$this->view('users/index', $data);
} else {
// success
if I echo out the session variable and view the page source, I can see that the hidden variable value for token is exactly the same as the session variable value. Why then do I keep getting my error message that says the CSRF token is invalid?
The strange thing is that I do exactly the same thing in another area of my website and it works just fine. Very confused...
I also know that the session is set because if I change the validation to just:
if(!isset($_SESSION['token'])) {
then the error goes away. So, it is something to do with the hidden field form token.
ASKER
Hi there. I changed from !== to != and still get the same error. After running your code, I get:
CSRF token invalid: 'ozzYHKIBsClRase0yQ+Fi9Jxn RAQdAk8L07 M62Hy7ng=' != '/GAwFb6QwNgAZuyHAV08E0KoV 5+0W7VJDAQ lnNCg3SA='
What would you suggest to remedy the scenarios you put to me? This is pretty much how I have been doing it for a while now and thought it was the "right" way to do it. Clearly not! Haha.
CSRF token invalid: 'ozzYHKIBsClRase0yQ+Fi9Jxn
What would you suggest to remedy the scenarios you put to me? This is pretty much how I have been doing it for a while now and thought it was the "right" way to do it. Clearly not! Haha.
ASKER
The function which creates the session token is:
function make_form_token() {
$token = base64_encode(openssl_random_pseudo_bytes(32));
$_SESSION['token'] = $token;
return $token;
}
ASKER
Another thing I noticed. Underneath the form I have a link to another page (forgot password). On the forgot password page I have a link back to login. If I start on the login page and then click on the link to go to the forgot password page, and then click on the link again to go back to login and submit the form. The tokens match. If I click submit again then they don't match. That is odd behavior.
ASKER
I figured it out but I don't know why it broke it.
had a slash after /users/ but if I remove it so it is /users without the trailing slash then it works just fine.
<form action="<?php echo URLROOT; ?>/users/" id="loginForm" method="post">
had a slash after /users/ but if I remove it so it is /users without the trailing slash then it works just fine.
ASKER
Well, that was short lived. The reset password page also gives me the CSRF error now.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
My guess is that your make_form_token() function is being called more times than you expect.
Indeed. I am still not 100% sure but in my index method, I put die() right at the beginning of it so that the index method never actually runs. This issue goes away after doing this. If I let the index method run as normal, the error appears again. So for some reason, the token is being generated twice. I didn't find the exact solution but all I did was rename the index method to login.
So, my url went from mysite.com/users to mysite.com/users/login and everything seems to work properly now.
I am keen to use your 'quick fix' in production. Would that be okay?
Sure - it will simply have a little bit more overhead but if you can identify the root cause then you can eliminate most of that overhead.
ASKER
Thanks!
POST = "12345"
SESSION = 12345
If that's the case, then !== would return true because of the different data types. A quick way to test this is to just change !== to != (remove one of the equals signs) and see if it works at that point.
I'd also suggest doing a var_export() in your invalid message:
if(!isset($_SESSION['token
$data['message'] .= "CSRF token invalid: " . var_export($_POST["form_to
}
Then re-test and see what the error message says.
On a side note, be mindful of what would happen if your users want to open up multiple tabs or use the back button on their browser. If you're only storing one valid token in your session and if you're overwriting it each time a new token is generated, then you're likely going to end up with quite a few "invalid token" messages that will frustrate your users. So make sure that you're first thinking through your workflow as if you were an average user and attempting to use your application the way you would normally use any other web application. What happens when you hit the Back button on different pages? What happens if you refresh on a screen that processes a form POST? What happens if someone right-clicks on a link and chooses to open it in a new tab so they have two tabs open at the same time for your application?