3
\$\begingroup\$

Please criticize as thoroughly as possible, even the smallest thing will be very useful for me.

I'm trying to create a safe and easy system to change for future projects. I am aware that it should have a lot more functionality (check email, access attempts, notification by email, etc.), but this is what I have at the moment. I am wondering if it is safe even now.

I want to continue working on it but I am not 100% sure if I'm on the right track or if I need to add or remove some code (I like it to be as short as possible).

login.sql

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";

-- Database: `login`

-- Table structure for table `users`
CREATE TABLE IF NOT EXISTS `users` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(15) NOT NULL,
  `user_email` varchar(40) NOT NULL,
  `user_pass` varchar(255) NOT NULL,
  `joining_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

config.php

<?php
// Other settings
session_start();

// Connect to the database
class Database {
 private $host = "localhost";
 private $db_name = "login";
 private $username = "root";
 private $password = "";
 public $conn;

 public function dbConnection() {
  $this->conn = null;
  try {
   $this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
    $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  } 
  catch(PDOException $exception)    {
   echo "Connection error: " . $exception->getMessage();
  }
  return $this->conn;
 }
}

// Functions for managing users
class USER {
    private $conn;
    public function __construct()   {
        $database = new Database();
        $db = $database->dbConnection();
        $this->conn = $db;
 }

    public function runQuery($sql)  {
        $stmt = $this->conn->prepare($sql);
        return $stmt;
    }

    public function register($uname,$umail,$upass)  {
        try {
            $new_password = password_hash($upass, PASSWORD_DEFAULT);            
            $stmt = $this->conn->prepare("INSERT INTO users(user_name,user_email,user_pass) VALUES(:uname, :umail, :upass)");
            $stmt->bindparam(":uname", $uname);
            $stmt->bindparam(":umail", $umail);
            $stmt->bindparam(":upass", $new_password);                                        
            $stmt->execute();   
            return $stmt;   
        }
        catch(PDOException $e) {
            echo $e->getMessage();
        }               
    }

    public function doLogin($uname,$umail,$upass)   {
        try {
            $stmt = $this->conn->prepare("SELECT user_id, user_name, user_email, user_pass FROM users WHERE user_name=:uname OR user_email=:umail ");
            $stmt->execute(array(':uname'=>$uname, ':umail'=>$umail));
            $userRow=$stmt->fetch(PDO::FETCH_ASSOC);
            if($stmt->rowCount() == 1) {
                if(password_verify($upass, $userRow['user_pass'])) {
                    $_SESSION['user_session'] = $userRow['user_id'];
                    return true;
                } else {
                    return false;
                }
            }
        }
        catch(PDOException $e) {
            echo $e->getMessage();
        }
    }

    public function is_loggedin() {
        if(isset($_SESSION['user_session'])) {
            return true;
        }
    }

    public function redirect($url) {
        header("Location: $url");
        exit;
    }

    public function doLogout() {
        unset($_SESSION['user_session']);
        return true;
    }
}
?>

index.php

<?php
 require_once('assets/config.php');
 // If you are not logged, redirects to login page.
 $session = new USER();
 if(!$session->is_loggedin()) {$session->redirect('login.php');}

  $auth_user = new USER();
  $user_id = $_SESSION['user_session'];
  $stmt = $auth_user->runQuery("SELECT * FROM users WHERE user_id=:user_id");
  $stmt->execute(array(":user_id"=>$user_id));  
  $userRow=$stmt->fetch(PDO::FETCH_ASSOC);
?>
<html>
<head>
 <meta charset="utf-8">
 <title>Welcome</title>
 <link href="assets/styles.css" rel="stylesheet">
</head>
<body>
 <div class="container">
  <h1>Hello, <?php echo $userRow['user_name'];?>! - <a href="logout.php">Logout</a></h1>  
  <hr/>
  <p>This is the user area, this content is private.</p>
 </div>
</body>
</html>

login.php

<?php
require_once('assets/config.php');
$login = new USER();

if($login->is_loggedin()!="") {
    $login->redirect('index.php');
}

if(isset($_POST['btn-login'])) {
    $uname = strip_tags($_POST['txt_uname_email']);
    $umail = strip_tags($_POST['txt_uname_email']);
    $upass = strip_tags($_POST['txt_password']);

    if($login->doLogin($uname,$umail,$upass))   {
        $login->redirect('index.php');
    }   else    {
        $error = "Wrong Details!";
    }   
}
?>
<html>
<head>
 <meta charset="utf-8">
 <title>Login</title>
 <link href="assets/styles.css" rel="stylesheet">
</head>
<body>
 <div class="container">
  <h1>Login or <a href="register.php">Register</a></h1>
  <hr/>
  <div class="error">
   <?php 
    if(isset($error)) {
      echo "<p class='error'>$error</p>";
    }
    if(isset($_GET['joined'])) {
      echo "<p class='success'>Successfully registered please login</p>";
    }
   ?>
  </div>
  <form method="post" id="login-form">
    <input type="text" name="txt_uname_email" placeholder="Username or Email"/>
    <input type="password" name="txt_password" placeholder="Password" />
    <button type="submit" name="btn-login">Login</button>
   </form>
 </div>
</body>
</html>

register.php

<?php
require_once('assets/config.php');
$user = new USER();

if($user->is_loggedin()!="") {
    $user->redirect('index.php');
}

if(isset($_POST['btn-signup'])) {
    $uname = strip_tags($_POST['txt_uname']);
    $umail = strip_tags($_POST['txt_umail']);
    $upass = strip_tags($_POST['txt_upass']);   

    if($uname=="")  {
  $error[] = "Provide username!";   
    }
    else if($umail=="") {
  $error[] = "Provide email!";  
    }
    else if(!filter_var($umail, FILTER_VALIDATE_EMAIL)) {
  $error[] = 'Please enter a valid email address!';
    }
    else if($upass=="") {
  $error[] = "Provide password!";
    }
    else if(strlen($upass) < 6){
  $error[] = "Password must be atleast 6 characters!";  
    } else {
        try {
            $stmt = $user->runQuery("SELECT user_name, user_email FROM users WHERE user_name=:uname OR user_email=:umail");
            $stmt->execute(array(':uname'=>$uname, ':umail'=>$umail));
            $row=$stmt->fetch(PDO::FETCH_ASSOC);

            if($row['user_name']==$uname) {
                $error[] = "Sorry username already taken!";
            } else if($row['user_email']==$umail) {
                $error[] = "Sorry email id already taken!";
            } else {
                if($user->register($uname,$umail,$upass)){  
                    $user->redirect('login.php?joined');
                }
            }
        }
        catch(PDOException $e) {
            echo $e->getMessage();
        }
    }   
}
?>
<html>
<head>
 <meta charset="utf-8">
 <title>Register</title>
 <link href="assets/styles.css" rel="stylesheet">
</head>
<body>
 <div class="container">
  <h1>Register or <a href="login.php">Login</a></h1>
  <hr/>
  <?php 
   if(isset($error)) {
    foreach($error as $error) {
    echo "<p class='error'>$error</p>";
    }
   }
  ?>
  <form method="post">
   <input type="text" name="txt_uname" placeholder="Username" value="<?php if(isset($error)){echo $uname;}?>" />
   <input type="text" name="txt_umail" placeholder="Email" value="<?php if(isset($error)){echo $umail;}?>" />
   <input type="password" name="txt_upass" placeholder="Password" />
   <button type="submit" name="btn-signup">Register</button>
  </form>
 </div>
</body>
</html>

logout.php

<?php
 require_once('assets/config.php');

 $user_logout = new USER();
 $user_logout->doLogout();
 $user_logout->redirect('index.php');
?>
\$\endgroup\$
4
  • 2
    \$\begingroup\$ Hey, welcome to Code Review! Maybe you could elaborate a bit more on what your code is actually supposed to do and not only on what you want out of a review. \$\endgroup\$
    – Graipher
    Commented Sep 8, 2016 at 7:47
  • \$\begingroup\$ @Graipher Hello, thank you very much for the welcome, sorry for my mistake, I put before the code in "StackOverflow" but a user told me that gets better here, that this product is special for reviewing code, I'm wrong? \$\endgroup\$ Commented Sep 8, 2016 at 19:06
  • \$\begingroup\$ Welcome to Code Review! I have rolled back the last edit. Please see what you may and may not do after receiving answers. \$\endgroup\$
    – Vogel612
    Commented Sep 8, 2016 at 23:32
  • \$\begingroup\$ @Vogel612 Thanks for the info, will not happen again. \$\endgroup\$ Commented Sep 9, 2016 at 2:27

4 Answers 4

5
\$\begingroup\$

Preamble

Writing a secure user management and authentication system is not an easy task. It has many pitfalls and you should follow a lot of guidelines and best practices along the way.

Security and your goal

I like it to be as short as possible

really don't fit together as you could throw things over board, that may have reduced your vulnerability.

As your example fulfills lots of functions, I'd like to focus on some parts in this answer:

Transforming the user input

The way you transform the user input has some flaws. During registration and login you manipulate the input like this:

$upass = strip_tags($_POST['txt_password']);

The question is: Why are you using strip_tags here? Of course you don't want to re-print user input with tags to avoid certain attacks.

But in terms of user experience consider the situation, which might be very confusing and frustrating:

  • A user tries a username called <mr>smith.
  • You strip tags and test it against your user table, where a user called smith is already present.
  • You show the registration form again with the value smith and the error "username already taken".

Another example where you're actively changing the password:

  • The users' password is <>h<>e<>l<>l<>o.
  • The user will not know that you have altered the password.
  • As long as you don't change your login/registration process this might work, but the user will not be able to login once your remove strip_tags for any reason.
  • Also, and even more dangerous: The complex password <>h<>e<>l<>l<>o becomes only a hashed (and salted) version of hello, which might be very easy to crack.

So instead of transforming the user's input, show messages that help the user to insert valid values, like "The username can't contain the symbols <>".

To avoid problems with tags in those values, don't output raw data and print the username or email using htmlentities.

Throwing exceptions

During your registration you somewhere catch an exception:

catch(PDOException $e) {
    echo $e->getMessage();
}

In a live setting, you should never present exceptions to the user. If an attacker somehow manages to raise that exception, he will be presented with insides of your system and database layout, that only you should have. Instead of printing the exception's message, present the user a default error and log the message into a log file on your server.

Configuration

Currently you have your database settings stored in the Database class itself:

class Database {
    private $host = "localhost";
    private $db_name = "login";
    private $username = "root";
    private $password = "";
}

As your project and your class hierarchy will grow and when you want to use your system in different projects, this will become really hard to maintain.

It would be better to store the settings in some sort of configuration file and inject them into the necessary classes.

Imagine, later you want to send registration emails to the user and you need to configure that as well. You now can keep all configuration in one place, without altering your classes.


Finally I would recommend to take look at the configuration and user management/authentication components of other PHP frameworks, like Symfony.

\$\endgroup\$
1
  • \$\begingroup\$ Corrections are ready you mention, you can look again the code, and this updated my question ... I do not stay clear what class Connection (last suggestion). I could not work at it, but for now I think it's not a major issue or security question? I want so far is that it is safe. \$\endgroup\$ Commented Sep 8, 2016 at 23:28
3
\$\begingroup\$

In addition to what has been mentioned already.

DB connection

I would suggest to define a charset in your PDO connection string, as good practice.

There are some rare border cases where PDO prepared statements are still prone to SQL injections, Ircmaxell explains this better than I can in his post.

$this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name .';charset=latin1;', $this->username, $this->password);

Sessions

To make it harder for Sessions to be hijacked you should regenerate your session id after login with session_regenerate_id();.

Also configuring PHP to keep your cookies safe will secure your login further.

Enabling session.cookie_httponly will prevent your cookies from being accessed by scripting languages and enabling session.cookie_secure will ensure that are cookies being send over HTTPS (Which I assume you will be using). Please look up the PHP manual for more information about these settings.

Also when the user log out, you can use session_destroy() to destroy all data registered to a session.

Output

Your form is currently not protected against CRSF, I suggest searching for a library or alternatively read this script on OWASP.

Currently your validation will throw an error as your uname, uname and upass variables are not being checked if they exist, I would tighten your validation, as an example:

if(isset($_POST['btn-signup'])) {    
   if( empty($_POST['txt_uname']) || !is_string($_POST['txt_uname']) )  {
      $error[] = "Provide username!";   
        }
    else if( empty($_POST['txt_umail']) ) {
      $error[] = "Provide email!";  
        }
    else if(!filter_var($_POST['txt_umail'], FILTER_VALIDATE_EMAIL)) {
      $error[] = 'Please enter a valid email address!';
     } 
    else if( empty($_POST['txt_upass']) ) {
      $error[] = "Provide password!";
    }
    else if(strlen($_POST['txt_pass']) < 6){
      $error[] = "Password must be atleast 6 characters!";  
    } else {
      // etc.
   }
}

In addition you can add a honey pot input field in your form to detect if the registration is spam. If this hidden input field has been filled in then you should not register the user as this is most likely spam.

\$\endgroup\$
1
  • \$\begingroup\$ And implement improvements, will soon be in another question. I'm breaking my head with all the answers (I'm very excited to receive many). Had planned not receive much help (which is great), I appreciate the time spent helping others. I will continue working on this project and have it ready as everything mentioned publishes a new post. THANKS FOR YOUR TIME! \$\endgroup\$ Commented Sep 9, 2016 at 2:50
3
\$\begingroup\$

Others have already mentioned some structural and style improvements, so I'll concentrate on the security aspect.

Security

You do a lot of things right. You use prepared statements and you use proper hashing. But there are also some vulnerabilities, and some things that violate security best practices.

XSS

You are vulnerable to reflected XSS here:

<input type="text" name="txt_uname" placeholder="Username" value="<?php if(isset($error)){echo $uname;}?>" />
<input type="text" name="txt_umail" placeholder="Email" value="<?php if(isset($error)){echo $umail;}?>" />

XSS can lead to data leakage (like cookies), bypassing of CSRF protection, injection of keyloggers, defacement, phishing, and so on.

strip_tags is not enough to defend against this:

" autofocus onfocus="alert(1)

For proper defenses, see below.

Relative Path Overwrite

You include your CSS file with a relative path, which leads to vulnerable code, as it makes CSS injection possible.

If an attacker can inject CSS code - which they can via the username - this injected CSS code can be parsed as CSS code by appending slashes at the end of the path. This can lead to phishing, defacement, limited data leakage, and other problems.

Filtering the username is not the correct solution for this, you need to include your CSS files via absolute path. You should also set a doctype as it mitigats this vulnerability in most browsers.

CSRF

Your code doesn't have CSRF protection. In case this isn't handled elsewere, you really need to include this, even for login pages.

An attacker could force-login a user in the hopes that they reveal sensitive data while logged into an attacker-controlled account, or they could exploit XSS issues in the user area.

Redirect

Your redirect function is vulnerable to open redirect. Currently, no user input is passed to it, but each function which uses potentially vulnerable functions - a redirect in this example - should be secure, in case it is used differently in the future.

Improper input sanitation

@insertusernamehere already pointed that calling strip_tags on a password is a bad idea. Their example is a bit contrived (a password of <>h<>e<>l<>l<>o), but it's a lot worse. a<super_secure!password becomes a. Users may not use < often, but password managers may.

Apart from that, calling either strip_tags or htmlentities on input is not a good approach at all. XSS is an output vulnerability, and that is where it needs to be defended against.

You should have input validation though! Ideally, via a generalized class that handles all input, and which provides methods such as getInt, getBool, getAlphaNum, getRaw, getSafeHTML, and so on. Then, always(!) access all input via these methods.

Proper XSS protection

XSS is an output vulnerability, so defend against it when printing. You also want to do this by default. Having to remember encoding each variable - or worse, thinking for each variable if encoding is necessary - will lead to mistakes sooner rather than later.

XSS is also context sensitive, meaning a call to strip_tags will not be enough in many situations. <input id="username" value="[USERNAME]">, <script>var username = "[USERNAME]";></script> would be two examples.

Use a decent templaing engine which HTML encodes by default, and use JavaScript or URL encoding when necessary.

SQL Credentials

Store your database credentials in a special file outside the webroot. This makes it easier to exclude them from version control, and avoids accidentally exposing automatically created backup files.

\$\endgroup\$
1
  • \$\begingroup\$ I am very new to PHP and I'm doing my best work to learn, I just want to create a simple insurance system as I have since I will be used only for me (I want to do a system to store information about my family business). So you see that there is nobody knows that page, possibly I and my father are the only ones to visit. But I want to sleep peacefully, not like that one day is all magically erase me. We appreciate very much for your time if you can let me examples with code, to implement it and then post a new question with the upgrade of all tell me I have to do. THANKS FOR YOUR TIME! \$\endgroup\$ Commented Sep 9, 2016 at 2:45
2
\$\begingroup\$

Good points by @insertusernamehere

I would like to strongly emphasize his points on thinking about what values in your code should be configuration driven vs. actually hardcoded, as well as the thought to look at some existing frameworks to understand how they approach these problems.

So, on to my thoughts:

Database table

  • Consider dropping user_* prefixes on your field names. You table is called users, so you should already have this context.
  • Don't truncate field names just for shortness sake. It is is more clear to call a password field password than it is pass. It would actually be better to call this field password_hash.
  • You likely want unique indexes on both user name and user email fields.

config.php

  • A file called config should have only config in it - no classes, no application bootstrapping.
  • Don't put all your classes into one file. Split them up into their own individual files. This makes your application more maintainable.
  • Indentation is inconsistent (this is pretty much a problem throughout all the code). Pick a style and stick with it, though I would suggest that single space indentation is not really indentation at all, just confusing.
  • Line length gets above ~ 80 characters (a good rule-of-thumb limit) in many case (typical throughout all code). Break you code up across lines as needed to help keep this line length down and make your code more readable.

Database Class

Your database class really holds little value. Typically one might implement a database class to do things such as:

  • Manage connections - something your implementation falls short of doing
  • Abstract away implementation details - something not happening here at all since classes working with the DB connection must understand that PDO is being used
  • Provide user-friendly query interfaces - again something not approached here.

About the only value your class has is in allowing calling classes to not have to understand how to instantiate a PDO object to work with. If you want to enforce that only a single connection is used for each script execution pass, then consider implementing a singleton here.

Even if you don't want to implement singleton, consider implementing the method to get the PDO object as a static method. There really is no reason at at all that calling code should have to first instantiate a Database object and then call the method to get the actual PDO object that it needs to work with.

Also consider naming this class and method more specifically to what it does. PDOProvider::getPDO() or PDOSingleton::getPDO() (if you try to go singleton route) would be much clearer usage from calling code.

Don't echo out Exception information. In fact, if you are not planning on actually doing something meaningful with an Exception (i.e. logging, wrapping in more general exception, rethrowing, or some combination of the above), you might be best not to put code that could throw Exception in try-catch at all. It would be better to let the Exception bubble up the call stack than to not do anything meaningful with it. This class should not care about end use messaging for the exception condition. This class just needs to do whatever is appropriate to a) log exception information and b) handle the exception in whatever manner is most meaningful for calling code to understand whatever it needs to know to determine how to handle the situation.

User class

  • Name your class User not USER.
  • Consider whether your User class itself should just describe the properties and behaviors of a User itself, or also have to have knowledge on how to create/find a user. You might have a helper class or classes which have the responsibility for creating / searching for users and instantiating valid user objects and limit the functionality of a user class to truly what the properties and behaviors of a user are. This class, as it stands, almost seems like more of a helper class (which shouldn't be named User at all) than an actual user class as there is really no user information stored on the class at all.
  • Consider passing a valid PDO object to the class as parameter in constructor. This class should not need to understand how to instantiate a PDO object. It should be given the dependencies it needs to operate (a concept called "Dependency Injection" which you should familiarize yourself with).
  • runQuery looks like a general PDO functionality that should not reside in a User class. This method is also improperly named, as it does not execute a query at all, just prepares one and returns PDOStatement object if successful. I honestly don't see what value this method is adding anyway.
  • I have same concerns in this class with basically meaningless try-catch exception handling blocks and improper responsibility for messaging end user.
  • In register function:
    • You should validate that you have reasonable parameters to work with (i.e. non-zero length strings) and fail out immediately with InvalidArgumentException or similar before executing any further. This is especially important since this is publicly exposed method.
    • $new_password is a really poor variable name. How about call it what it is - $password_hash. Try to always useful meaningful variables that are specific to a) what the content of the variable is intended to be and/or b) how the content of the variable is intended to be used.
    • This code only considers happy path. What happens if insert fails?
    • Why return PDOStatement object? What do you expect the caller to do with it? Perhaps this method either just returns boolean on failure/success, or perhaps returns the autoincrement id generated for the inserted record.
  • doLogin() seems odd to me. To me perhaps this method should return a valid User object (against which user can derive whatever information it wants) or null/false on failure. This relates to my question as to whether this sort of method should be part of user class. Right now it does things like set session values, which should not be within the responsibility of a user class IMO. I also don't really understand the user name OR user email login capability here. It seems like you could potentialy get multiple records in your result set. If you need to support this use case, I would have two different methods like getUserByName and getUserbyEmail (possibly static methods) and call which is appropriate by whatever the user provides (don't have them provide both). Leave the "log in" aspect of the logic outside the user class. As it stands, this method also lacks input validation and only really considers happy path (what if empty result set is received from query?)
  • Other methods in this class: Do they really belong here? Why would a redirect function be in a user class? Why are methods on this class interacting with session rather than actual properties on the class? True logout functionality may require a lot more than simply unsetting a session variable. Do you regenerate session id, do you reset cookies? How much d you really need to reset the application back to a state where the a brand new user can sit down on that machine and not know anything about previous user's session? Again this should all likely not be managed in a "user" class.

index.php

  • You can see just how poorly your User class is defined in this section of code (see my added comments)

Your code:

 // why would you call this variable $session if you were truly getting
 // a user object here?
 $session = new USER();
 // Why include hard-coded link to redirection page?  If you ever want
 // to change this, you have to go to every page where this code is used
 // to change it, rather than having someplace where you have configured
 // in your application where the user should be directed for login screen.
 if(!$session->is_loggedin()) {$session->redirect('login.php');}

  // why should you need to re-instantiate the user again here?
  $auth_user = new USER();
  // if you had a meaningful User class, I would think the id would
  // be stored on the object rather than needing to be derived from
  // session variable.
  $user_id = $_SESSION['user_session'];
  // Why should calling code here need to know how to get user info from DB?
  // You should be able to pass in an id to instantiate user class and
  // get an object with all appropriate properties filled out
  $stmt = $auth_user->runQuery("SELECT * FROM users WHERE user_id=:user_id");
  $stmt->execute(array(":user_id"=>$user_id));  
  $userRow=$stmt->fetch(PDO::FETCH_ASSOC);

If you follow some of the suggestions I have noted thus far, you might get to some code like this:

 // this code somewhere in bootstrap file (not config)
 // setup PDO dependency
 try {
     $pdo = PDOProvider::getPDO();
 } catch (Exception $e) {
     // perhaps log exception here
     // then redirect to error page or message user
 }

 // set up session
 session_start()

 // instantiate object to manage session behaviors -
 // log in, session regeneration, redirection,
 // interact with $_SESSION variables, etc.
 // Here I am using a theoretically named UserSessionManager class
 // perhaps it needs PDO as a dependency to do login operations and such.
 // The theoretical methods on this class are for informational purposes
 // only.  I honestly don't know if you want to have to instantiate an
 // object here, as some of the methods may be good for static usage.
 $user_session_manager = new UserSessionManager($pdo);


 // now the code below could be in any page where you needed to
 // enforce logged in users and work with user information

 // See if there is logged in user
 if($user_session_manager.isUserLoggedIn() === false) {
     $user_session_manager.redirectToLogin();
 }

 // since we know we have a valid user login at this point,
 // let us retrieve a user object
 $user = $user_session_manager.getUser();

When you take out the comments from the suggested coding approach I noted above, you can see that the calling code (index.php page) would now need to know very little about the underlying mechanisms for managing the user login, the database, etc. and now only contains a handful of lines of code to get your user information set up in a state that is meaningful for the page.

Login/registration

  • Consider user experience around registration. Why would you want to send them to login form after registration? Presumably if you had valid information for a registration, then you could just put user directly into logged in state, rather than forcing them to then log in.
  • Don't pass log in/account creation state via GET. This is way to easy to hack. This information should always be in session. For example, say you had successful registration and wanted to then redirect user to home page (rather than login page). You could store an account created flag either in session or in the theoretical UserSessionManager class which could be checked to see if any messaging should be displayed to the user and then flag turned off.
  • You should consider more significant validation of user input fields:
    • Do fields like username and password need to be a minimum length or have any constraints on characters it can use?
    • Don't nest validation in if-else conditionals. Why not validate all field with every pass so you can give user full feedback at once.
    • For registration, consider adding password verification field so you can help user mitigate typos in their password.
    • Consider using more robust validation class, or perhaps have static validation methods on user class, so that you can simply call centralized validation functions rather than having validation logic exist on every page.
  • Consider whether you REALLY want to tell users if there name or email matches/doesn't match the system on failed registration/login attempts. Knowing whether a user account exists or not is a valuable piece of potential information for an attacker. Many developers prefer the more vague "user account could not be create" or "login not successful" which are more non-specific.
  • If you don't REALLY need the functionality noted above, you can do away with needing to perform a select before the insert when registering an account. If your table has proper unique index on user name and user email, an insert that would violate that uniqueness constraint will be unsuccessful. You just need to be able to handle that use case in your method that inserts the user record. This eliminates 50% of your SQL calls for registration use case.
  • If you choose to keep the unnecessary select before insert, you should at least move the functionality to lookup a user by email or user name into a class method and out of the logic of the registration page.
  • Consider using empty() or !empty to check "truthiness"/"falsiness" of variables for validation rather than == ''. You should ideally only use loose comparison for very specific reasons, defaulting your code writing approach to using strict comparisons as a default. This makes your code less fragile to "truthiness"/"falsiness" bugs and clearer to read with regards to intent. For me, if I run into a case where it makes sense to use a loose comparison, I make sure to add a comment in code as to why I this made decision.

Logout

  • You should not send user to index page here if index is then going to redirect user to login page anyway. Just redirect to login page.
  • I think this could be simplified to just:

    // not shown - include bootstrap file

    $user_session_manager->logout();

Where within the UserSessionManager class the logout method might call methods to unset session, regenerate session id, unset current User object, clear cookies, etc. and then finally call it's own redirectToLogin() method. This takes the logout logic out of individual pages and into a class where it can be centrally maintained.

\$\endgroup\$
5
  • \$\begingroup\$ This response was more thinking I leave, right now I'm bursting of the head, not where to begin to correct this. I consider myself a perfectionist (I always look straight ahead and ordered), I take a long time to read and understand your answer, gave right where I wanted someone to give, I need to do this from scratch. It gives me great courage not to do that, I have no knowledge yet. I will work very hard on this. THANKS FRIEND! P.S. You think it's possible to modify my code (only those files) to a degree of perfection and absolute safety? or better stop and occupied some CMS? \$\endgroup\$ Commented Sep 9, 2016 at 4:37
  • \$\begingroup\$ @DavidNoriega I have sort of a love/hate relationship with PHP frameworks. I think they can be really good for building applications with some tooling that you feel comfortable has reasonable security measures in place and provides a good base level of functionality for you. I think if a developer relies solely on frameworks, their individual skill development may stagnate. At some point, if you want to be an advanced software developer/engineer, you will need to understand some of the things that frameworks abstract away from you. In that regard, frameworks can be good for learning. \$\endgroup\$
    – Mike Brant
    Commented Sep 9, 2016 at 19:15
  • \$\begingroup\$ I spent thinking all night about it that you say, I think it would be best to start with something already done, this way I'm understanding how it works may learn more, I'm thinking of using "userspice" I'm seeing the official videos and I like how it works, the code is not so great and I can understand, I see that is based on "usercake" which I think is fine. In your personal opinion, I would you recommend "userspice"? It is safe? because I'm about to start to read your code and understand it, but I do not like to waste time and ultimately stay the same. \$\endgroup\$ Commented Sep 9, 2016 at 19:26
  • \$\begingroup\$ @DavidNoriega Not familiar with userspice to comment. Whether you consider furthering this current codebase a waste or not is really up to you. If you are learning form doing this in a way that you learn best, that is probably not wasted time. If you learn best by looking over some existing code, then go that route. \$\endgroup\$
    – Mike Brant
    Commented Sep 9, 2016 at 19:31
  • \$\begingroup\$ Thank you very much, then keep going, do not find much information (opinions) on "UserSpice" so I'm posting a question, possibly someone else study the code and can say something about safety, because if this is safe I think I have very clear the end of the tunnel. THANKS FOR YOUR TIME BOSS! \$\endgroup\$ Commented Sep 9, 2016 at 19:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.