0

I am trying to use password_verify in a project and despite using this function before without any issues, it is no longer working for me. I feel like I am overlooking something really small. Following is some of my code:

case 'Register':
    $first_name = trim(filter_input(INPUT_POST, 'first_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
    $middle_name = trim(filter_input(INPUT_POST, 'middle_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
    $last_name = trim(filter_input(INPUT_POST, 'last_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
    $email = trim(filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL));
    $password = trim(filter_input(INPUT_POST, 'password', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
    $phone = trim(filter_input(INPUT_POST, 'phone', FILTER_SANITIZE_FULL_SPECIAL_CHARS));

    $email = checkEmail($email);
    $password = checkPassword($password);

    if (checkExistingEmail($email)){
        $_SESSION['message'] = '<p class="formErrorMessage">That email address is already in use. Try logging in or using a different email.</p>';
        include '../view/register.php';
        exit;
    }

    if (!empty($phone)){
        if (checkExistingPhone($phone)){
            $_SESSION['message'] = '<p class="formErrorMessage">That phone number is already in use. Please use a different phone number.</p>';
            include '../view/register.php';
            exit;
        }
    }

    if (empty($first_name) || empty($last_name) || empty($email) || empty($password)){
        $_SESSION['message'] = '<p class="formErrorMessage">Please provide information for all required form fields.</p>';
        include '../view/register.php';
        exit;
    }

    $hashedPassword = password_hash($password, PASSWORD_DEFAULT);

    $registrationOutcome = registerUser($first_name, $middle_name, $last_name, $email, $hashedPassword, $phone);

    if($registrationOutcome === 1){
        setcookie('first_name', $first_name, strtotime('+1 year'), '/');
        $userData = getUser($email);

        $_SESSION['loggedin'] = TRUE;

        array_pop($userData);

        $_SESSION['clientData'] = $clientData;

        header('Location: /valleymusicclub/accounts/');
        exit;
    } else {
        $_SESSION['message'] = "<p class='formErrorMessage'>Sorry, $first_name, but the registration failed. Please try again.</p>";
        include '../view/login.php';
        exit;
    }
    break;
case 'Login':
    $email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
    $email = checkEmail($email);
    $password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $passwordCheck = checkPassword($password);

    if (empty($email) || empty($passwordCheck)){
        $_SESSION['message'] = '<p class="formErrorMessage">Please provide a valid email address and password.</p>';
        include '../view/login.php';
        exit;
    }

    $userData = getUser($email);

    $passwordHash = $userData['password'];

    $hashCheck = password_verify($password, $passwordHash);

    if (!$hashCheck){
        $_SESSION['message'] = "<p class='formErrorMessage'>Please provide a valid password. $password</p>";
        include '../view/login.php';
        exit;
    }

    $_SESSION['loggedin'] = TRUE;

    array_pop($clientData);

    $_SESSION['clientData'] = $clientData;

    header('Location: /valleymusicclub/accounts/');
    exit;

The thing that is baffling me is that this is almost the exact same code structure as another project I did a while back and used as a base, which prior project is STILL working on the same machine. After doing a var_dump on the function, I verified that it always returns false, even after ensuring multiple times that I am using the same password as originally put in the database.

I did some research looking into the common pitfalls for this issue. I verified that my database is a VARCHAR with 255 limit to account for the minimum 60 characters for default hash. I checked that the password was hashing once going into the database, which it is. I verified that the $userData['password'] variable holds the hashed password, which it does. I verified that the password I am passing through to the $password variable through the form is indeed the password that I typed in, which it is. I also made sure that I am not passing a hashed password in the first parameter of the function and that I am passing a hashed password as the second parameter. I am honestly at a loss. It just doesn't make sense that the same code works in a different project and not this one, even after quadruple checking everything that could go wrong.

Edit: I understand that filters can affect these things, but as I said before, this code is taken straight from a project that is working as intended, even with filters. Following is code that is working currently:

case 'register':
    $clientFirstname = trim(filter_input(INPUT_POST, 'clientFirstname', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
    $clientLastname = trim(filter_input(INPUT_POST, 'clientLastname', FILTER_SANITIZE_FULL_SPECIAL_CHARS));
    $clientEmail = trim(filter_input(INPUT_POST, 'clientEmail', FILTER_SANITIZE_EMAIL));
    $clientPassword = trim(filter_input(INPUT_POST, 'clientPassword', FILTER_SANITIZE_FULL_SPECIAL_CHARS));

    $clientEmail = checkEmail($clientEmail);
    $checkPassword = checkPassword($clientPassword);

    $existingEmail = checkExistingEmail($clientEmail);

    // Check for existing email address in the table
    if($existingEmail){
        $message = '<p class="formErrorMessage">That email address already exists. Do you want to login instead?</p>';
        include '../view/login.php';
        exit;
    }

    // Check for missing data
    if(empty($clientFirstname) || empty($clientLastname) || empty($clientEmail) || empty($checkPassword)){
        $message = '<p class="formErrorMessage">Please provide information for all empty form fields.</p>';
        include '../view/registration.php';
        exit;
    }

    // Hash the checked password
    $hashedPassword = password_hash($clientPassword, PASSWORD_DEFAULT);

    // Send the data to the model
    $regOutcome = regClient($clientFirstname, $clientLastname, $clientEmail, $hashedPassword);

    // Check and report the result
    if($regOutcome === 1){
        setcookie('firstname', $clientFirstname, strtotime('+1 year'), '/');
        $_SESSION['message'] = "<p class='formSuccessMessage'>Thanks for registering, $clientFirstname. Please use your email and password to login.</p>";
        header('Location: /phpmotors/accounts/?action=login');
        exit;
    } else {
        $_SESSION['message'] = "<p class='formErrorMessage'>Sorry, $clientFirstname, but the registration failed. Please try again.</p>";
        include '../view/login.php';
        exit;
    }
    break;
case 'Login':
    $clientEmail = filter_input(INPUT_POST, 'clientEmail', FILTER_SANITIZE_EMAIL);
    $clientEmail = checkEmail($clientEmail);
    $clientPassword = filter_input(INPUT_POST, 'clientPassword', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $passwordCheck = checkPassword($clientPassword);

    // Run basic checks, return if errors
    if (empty($clientEmail) || empty($passwordCheck)) {
        $_SESSION['message'] = '<p class="formErrorMessage">Please provide a valid email address and password.</p>';
        include '../view/login.php';
        exit;
    }
    
    // A valid password exists, proceed with the login process
    // Query the client data based on the email address
    $clientData = getClient($clientEmail);
    // Compare the password just submitted against
    // the hashed password for the matching client
    $hashCheck = password_verify($clientPassword, $clientData['clientPassword']);
    // If the hashes don't match create an error
    // and return to the login view
    if(!$hashCheck) {
        $_SESSION['message'] = '<p class="formErrorMessage">Please check your password and try again.</p>';
        include '../view/login.php';
        exit;
    }
    // A valid user exists, log them in
    $_SESSION['loggedin'] = TRUE;
    // Remove the password from the array
    // the array_pop function removes the last
    // element from an array
    array_pop($clientData);
    // Store the array into the session
    $_SESSION['clientData'] = $clientData;
    // Send them to the admin view
    header('Location: /phpmotors/accounts/');
    exit;

I also tried removing the filters, trim, etc., and it still is not working. Yes, I verified that the trimmed/filtered password is producing a string that matches my original password. I would agree that using filters before verifying that it works in the first place is the right way to go, but I have used this structure as demonstrated here and it works just fine in the prior project. I don't understand what is different between them.

Edit 2: I just added code to each of my projects to compare their outputs. Here is the code I added to the project that is working:

    $clientData = getClient($clientEmail);

    // var dumps for troubleshooting purposes
    var_dump($clientPassword);
    var_dump($clientData['clientPassword']);

    // Compare the password just submitted against
    // the hashed password for the matching client
    $hashCheck = password_verify($clientPassword, $clientData['clientPassword']);

    var_dump($hashCheck);

Following is the output: string(12) "Password123@" string(60) "$2y$10$gPmIdgHMtKEa28EagP4H3.TDZrWwoMY/CABIQleOqXUzh65/fov6W" bool(true)

Here is the code I added to the project currently having trouble:

    $userData = getUser($email);

    $passwordHash = $userData['password'];

    var_dump($password);
    var_dump($passwordHash);

    $hashCheck = password_verify($password, $passwordHash);

    var_dump($hashCheck);

This is the output: string(12) "Password123@" string(60) "$2y$10$8wheUb4qjxl.V1qM5EsKf.nE.MEF5wE8rnfnDApIMRjpIFxhgVt/2" bool(false)

Just a quick note: all I added were var_dumps. I did not change anything else about the code.

  • 3
    I'd recommend you do away with the filters unless you really know what they're doing and why you're using them. Specifically `FILTER_SANITIZE_FULL_SPECIAL_CHARS` will encode any HTML special characters, so your password_has() calls aren't hashing what you think they are. The damaged data is likely to come back and bite you elsewhere. Too often we see programmers applying arbitrary 'sanitaisation' techniques as some attempt to defend against SQL injection. Don't mess with the data you're given unless you really understand the implications. – Tangentially Perpendicular Aug 10 '23 at 03:09
  • Could you check and verify that your FILTER_SANITIZE_FULL_SPECIAL_CHARS is not producing a [0 length string](https://www.php.net/manual/en/filter.filters.sanitize.php#:~:text=Equivalent%20to%20calling,flags%20to%200.) – Neil Aug 10 '23 at 04:19
  • Why don't you just write down the password and the hash generated in the registration part and then compare them with the entered password and the hash from DB in the login part? I mean, isn't it the most down to the earth verification? – Your Common Sense Aug 10 '23 at 06:11
  • I understand the effects of sanitization, but (and I added this in an edit in my question) I tested it out without the sanitization and trims and it gives me the same results. Moreover, this code structure works in a prior project that I did. I do have some experience with this stuff and it has worked in the past. That is why I am so baffled that it's not working now. – Brandon Lisonbee Aug 10 '23 at 17:20
  • I did write down the password and hash that is being generated in the registration and the password that is coming through on the login portion. I did some echos and var_dumps to verify that information is coming through as intended, as well as to verify that information is being pulled from the database, and all of that is working correctly. I just keep getting false for password_verify() – Brandon Lisonbee Aug 10 '23 at 17:22
  • Oh, then surely you can provide a proof, a something like [this](https://phpize.online/sql/mysql57/undefined/php/php81/965a73e0fd254acc9f701828288ed0d1/) – Your Common Sense Aug 10 '23 at 17:33
  • @BrandonLisonbee Please [edit] your question to include the output of `var_dump($clientPassword, $clientData['clientPassword']);` right before your `password_verify()` call. – Progman Aug 10 '23 at 17:37
  • @Progman just did so, including adding a var_dump to show the bool variable – Brandon Lisonbee Aug 10 '23 at 18:49
  • @BrandonLisonbee The hash simply doesn't match for the given password. The hash for the password `Password123@` and the given seed from the hash you have should be `$2y$10$8wheUb4qjxl.V1qM5EsKf.pMkEafVWHxobdKt7PoWjNZB.YOZEyrq`, not `$2y$10$8wheUb4qjxl.V1qM5EsKf.nE.MEF5wE8rnfnDApIMRjpIFxhgVt/2`. It looks like you created the hash in a wrong way. – Progman Aug 10 '23 at 19:00
  • @Progman I might just not know much about password hashing, but what am I doing wrong? I am using the PASSWORD_DEFAULT algorithm and nothing else. What could be going wrong here that isn't in my other project? I did read up on the docs for this on php.net and saw nothing more I could do short of just writing my own cost and salt but it doesn't seem like that is necessary. – Brandon Lisonbee Aug 10 '23 at 19:32
  • @BrandonLisonbee Edit your question to add a `var_dump($clientPassword, $hashedPassword);` call after you called the `password_hash()` function. Then compare the same output with the other `var_dump()` at the `password_verify()` call. Then [edit] your question to include the output of both `var_dump()` calls. Indicate which output is from which `var_dump()` call. – Progman Aug 10 '23 at 19:36
  • @Progman I just found the issue. When checking the password to ensure it matched a regex pattern, I accidentally put the bool from that check in the $password variable rather than its own $checkPassword variable. Thank you everyone for your help. I am sorry I overlooked that and took so much time from you all. I am new to the site, should I take my question down since the issue was a non-issue and has been answered elsewhere? – Brandon Lisonbee Aug 10 '23 at 19:41

0 Answers0