0

Using xampp for local development, I was able to build a very simple authentication for accessing a webpage. Everything worked fine on my local machine but I ran into some issues while testing it on my live server.

Instead of creating the database in phpMyAdmin, I had to use the MySQL Databases tool in my cPanel. No big deal, I guess, just create the myLogin database, then go into phpMyAdmin to add the myUsers table with ID, username, and password, and finally insert the user 'admin' with a hashed password.

Issue 1: I guess it only makes sense, but the following resulted in an error:

$DATABASE_HOST = 'localhost';
$DATABASE_USER = 'root';
$DATABASE_PASS = '';
$DATABASE_NAME = 'myLogin';

Failed to connect to MySQL: Access denied for user 'root'@'localhost' (using password: NO)

So, I opened mySQL Database again and added a user with a password and attached it to the myLogin database. I edited the code in the auth.php file to reflect the changes and... no error! However, no nothing.

Issue 2: After I enter the password and hit enter the browser just goes to the auth.php file and does nothing. No echoes, no redirect upon success, nothing. Below is the entire auth.php file:

<?php
session_start();
$DATABASE_HOST = 'localhost';
$DATABASE_USER = 'myUser';
$DATABASE_PASS = 'myPass';
$DATABASE_NAME = 'myLogin';

$con = mysqli_connect($DATABASE_HOST, $DATABASE_USER, $DATABASE_PASS, $DATABASE_NAME);
if ( mysqli_connect_errno() ) {
    die ('Failed to connect to MySQL: ' . mysqli_connect_error());
}
if ( !isset($_POST['uName'], $_POST['pWord']) ) {
    die ('Please fill both the username and password field!');
}
if ($stmt = $con->prepare('SELECT id, password FROM myUsers WHERE username = ?')) {
    $stmt->bind_param('s', $_POST['uName']);
    $stmt->execute();
    $stmt->store_result();

    if ($stmt->num_rows > 0) {
        $stmt->bind_result($id, $password);
        $stmt->fetch();
        if (password_verify($_POST['pWord'], $password)) {
            session_regenerate_id();
            $_SESSION['loggedin'] = TRUE;
            $_SESSION['name'] = $_POST['uName'];
            $_SESSION['id'] = $id;
            header('Location: ../wica.php');
        } else {
            echo 'Incorrect password!';
        }
    } else {
        echo 'Incorrect username!';
    }

    $stmt->close();
    }
?>

As I mentioned, I am a complete PHP newbie, this has all been cobbled together thanks to various tutorials. I don't know why it won't even echo "Incorrect password!" when I type the wrong password. It just sits on auth.php and does nothing and I'm clueless. This is my first time using mySQL on the remote server. Could there be critical settings that aren't the same as my xampp setup? Did I miss an important step somewhere?

Also, it seems a little insecure to have the DATABASE_USER and DATABASE_PASS just sitting in the auth.php file for anyone to see. Was I not supposed to do this? What are the secure alternatives, or are there any?

Frankenscarf
  • 1,189
  • 7
  • 10
  • you are mixing procedural and object oriented programming that not good and you didn't add error handling to catch errors. – nbk Feb 26 '20 at 22:08
  • Please forgive my utter ignorance, but you want me to add this bit and see what happens? ...{ print_r($con->error_list); } $con->close(); – Frankenscarf Feb 26 '20 at 22:11
  • haven't done MySQL in a while but based on observation it seems that your third if statement may have something incorrect withing the prepare. – Ahm23 Feb 26 '20 at 22:18
  • Added it, but nothing new. The only thing that shows in my console is "The character encoding of the HTML document was not declared. The document will render with garbled text in some browser configurations if the document contains characters from outside the US-ASCII range. The character encoding of the page must be declared in the document or in the transfer protocol." But that's just because it's trying to display the freaking auth.php file. – Frankenscarf Feb 26 '20 at 22:19
  • @fyrye No the contents aren't displayed, but the URL changed from the index.html to scripts/auth.php – Frankenscarf Feb 26 '20 at 22:23
  • @fyrye I added that and now when the auth.php page loads, it just displays "Array ( )" – Frankenscarf Feb 26 '20 at 22:26
  • Well, that's good, I guess? Also, the screenshot would just be a big blank white page, I promise, so I'm not going to the trouble. – Frankenscarf Feb 26 '20 at 22:31
  • Ok, so I just noticed an error_log file in the scripts dir. (I didn't know it did that) - "PHP Fatal error: Call to undefined function password_verify() in scripts/auth.php on line 34" Sorry I didn't know about these log files sooner, however, it doesn't help me because I'm an idiot trying to use turnkey php code. Please have pity and give me some direction? – Frankenscarf Feb 26 '20 at 22:49
  • Thank you @fyrye, looks like I'm working off 5.4.45. Removing password_verify definitely works, but does that mean I don't have any options when it comes to password encryption? Also, including the DATABASE_USER and PASS in the auth.php file still seems crazy insecure. Are there alternatives? Should I create a new question to deal with that since this comment train is huge? ;) – Frankenscarf Feb 26 '20 at 23:16
  • Rephrase/title your question, remove the preceding comments that were in attempt to troubleshoot the issue. Yes, there are many alternatives to storing database credentials and [questions](https://stackoverflow.com/q/32513480/1144627) regarding just that issue, but would be off-topic for SO. `password_verify`, can be [shimmed](https://github.com/ircmaxell/password_compat) in unsupported versions, or check your hosting provider, for options to upgrade PHP versions. I also suggest updating your dev environment to match, possibly using docker or vagrant to simplify segregation of environments. – Will B. Feb 26 '20 at 23:30
  • For database credentials and other sensitive data storage/retrieval with PHP, I prefer using [`DotEnv`](https://symfony.com/doc/current/components/dotenv.html) Any answers on SO will have their own benefits and drawbacks, and are more so suggestions on different approaches, so ultimately are determined to be off-topic on SO, as there is no single correct approach. – Will B. Feb 26 '20 at 23:34
  • Looks like I have the ability to change to a newer version of PHP on my server. Seems like 5.6 would be enough to enable the password encryption, 7+ seems like overkill considering my ignorance of PHP. I will look into docker & vagrant, thanks for the suggestion, I'll also see about matching my xampp enviro. to the remote server. And thanks for info on DotEnv. If my poor brain doesn't simply implode soon, I'll look into it too. – Frankenscarf Feb 26 '20 at 23:38
  • PHP 5.6 is [no longer supported](https://www.php.net/supported-versions.php). And security vulnerabilities will not be patched. PHP 7.2+ would be the best version to upgrade, as it performs dramatically faster than PHP 5.x, and the PHP docs and other libraries would be most compatible with 7.2+ PHP 8.0 is also expected to be released soon, but personally would not utilize it until it is 8.2+ as there are usually dramatic changes between x.0-x.4. – Will B. Feb 26 '20 at 23:42
  • Understood. Thanks again! – Frankenscarf Feb 26 '20 at 23:50
  • Please check the different [migration guides](https://www.php.net/manual/en/appendices.php) to ease the transition from 5.x to 7.x. It can be a TON of work, but is worth it in the long-run. Better to have a supported version than to have your site exploited by a security vulnerability. I also recommend ditching XAMP, since you can match your dev and prod environments very easily in a virtual machine. Check out [PuPHPet](https://puphpet.com/) for a simple UI to get you started. – Will B. Feb 26 '20 at 23:54

1 Answers1

0

In case you got caught in the wheels of the comment-train, here's the answer:

My local dev. server (xampp) is running PHP 7+ while my remote server's PHP version was 5.4x, preventing password_verify from working. I was able to update the remote server's PHP version and everything worked. (Thank you fyrye!)

As was pointed out, the php code itself was a mess, so I found a better tutorial, I hope, and re-wrote the auth file:

<?php
if ( isset($_POST['pWord'])) {
    require '../../../dbh.inc.php';

    $username = $_POST['uName'];
    $userpass = $_POST['pWord'];

    if(empty($username) || empty($userpass)) {
        header("Location: ../index.html?error=emptyFields");
        exit();
    } 
    else {
        $sql = "SELECT * FROM users WHERE username=?";
        $stmt = mysqli_stmt_init($conn);
        if(!mysqli_stmt_prepare($stmt, $sql)) {
            header("Location: ../index.html?error=sqlerror");
            exit();
        } 
        else {
            mysqli_stmt_bind_param($stmt, "s", $username);
            mysqli_stmt_execute($stmt);
            $result = mysqli_stmt_get_result($stmt);
            if($row = mysqli_fetch_assoc($result)) {
                $pwdCheck = password_verify($userpass, $row['password']);
                if($pwdCheck == false) {
                    header("Location: ../index.html?error=wrongPass");
                    exit();
                } 
                else if($pwdCheck == true) {
                    session_start();
                    $_SESSION['loggedin'] = TRUE;

                    header("Location: ../wica.php");
                    exit();
                } 
                else {
                    header("Location: ../index.html?error=wrongPass");
                    exit();
                }
            } 
            else {
                header("Location: ../index.html?error=noUser");
                exit();
            }
        }
    }
} 
else {
    header("Location: ../index.html?error=justWrong");
    exit();
}
?>

You might notice that I also moved the database credentials to an include that now resides above the public root.

Is it the best solution? Probably not, but learning how to implement something like DotEnv is beyond me atm. I don't actually know PHP, I just use it sometimes like a pleb. Plus, I just spent a month translating 3000+ lines of Actionscript into javascript and creating html/css equivalents for all the .swf library elements. My brain is currently oatmeal. Hope my follies can at least help someone else.

Frankenscarf
  • 1,189
  • 7
  • 10
  • If possible, I suggest using `PDO` instead of `MySQLi`, typical usage of PDO is significantly easier to use, unless you need specific features of MySQLi. Additionally, if you are not able to migrate, I highly suggest using the Object Oriented methods of MySQLi to reduce overhead/complications, and remove the redundant `else` after `exit` lines, or swapping out with `try/catch` and throw [exceptions](https://www.php.net/manual/en/language.exceptions.php) to handle the conditions, so your code would look something like: https://3v4l.org/5bQWu – Will B. Feb 27 '20 at 17:25
  • First, thanks for the follow-up! I can immediately see the cleanup benefits with try/catch and throw exceptions. Three questions: [1] The `$driver` variable, should this be in the dbh.inc file with the `$con` declaration? [2] Do I need `if(!$con) { throw new \InvalidArgumentException('Connection failed');}` in dbh.inc? [3] How does one absolute path to above the root? I was thrilled when the relative worked and just kept with it for the other header locations. Is it 100% necessary? – Frankenscarf Feb 27 '20 at 18:51
  • On the server-side, use `__DIR__` to obtain the full path to your current directory, then you can use relative denotations `/..`, this will work as an absolute path. On the client-side, such as for issuing redirects, use `/` for the `DOCUMENT_ROOT`. So `/index.html` or `/subdirectory/script.php`, etc The reason is to prevent migration issues should you need to migrate servers/systems, where the paths may change or you publish your code for others to use in their applications. – Will B. Feb 27 '20 at 19:29
  • Yes, the `$driver` should be included with your `new mysqli()` instatation script. Since you are using `new mysqli()`, if it fails an exception will be thrown, since it will always return an instance of `mysqli`. You would use [`if ($con->connect_error){ throw new \RuntimeException('Connection Failed') }`](https://www.php.net/manual/en/mysqli.connect-error.php). – Will B. Feb 27 '20 at 19:37
  • `InvalidArgumentException` was just used an example. Ideally you would use more descriptive exceptions, that you create specifically for your application. Such as `class BadCredentialsException extends RuntimeException{}`, or `UserAuthenticationException`, etc so you can handle them specifically. For example, you can catch a `DatabaseConnectionException` and handle it how you desire, like sending an email, etc. See: [`set_exception_handler`](https://www.php.net/manual/en/function.set-exception-handler.php) – Will B. Feb 27 '20 at 19:44
  • @fyrye I can't thank you enough for all your help and guidance. Your explanations for best-practices are very appreciated. There's nothing worse than a shaky foundation, right? One final question, if I may... When I was using the `MySQLi` version, I had to enable the `nd_mysqli` extension and disable the `mysqli` one for it to work. I'm not having any issues right now, but should I change those back? – Frankenscarf Feb 27 '20 at 20:09
  • `nd` refers to the Native Driver, which is the preferred and as of 5.4+ the default driver used for PDO and Mysqli. The Native Driver offers more optimizations and compatibility than the original `libmysql` driver. See: https://www.php.net/manual/en/mysqli.installation.php and https://stackoverflow.com/q/53871567/1144627 for more information, but I presume the `nd_mysqli` extension is provided with your specific environment and may not be available elsewhere. – Will B. Feb 27 '20 at 20:26
  • Looking back, it was the `mysqli_stmt_get_result` that required the `nd_mysqli` extension. After reading a bit, I see no reason to change them back as of now. Thanks again! – Frankenscarf Feb 27 '20 at 20:48