{"id":42,"date":"2015-03-25T11:52:06","date_gmt":"2015-03-25T03:52:06","guid":{"rendered":"http:\/\/woohuiren.me\/blog\/?p=42"},"modified":"2015-04-05T14:53:46","modified_gmt":"2015-04-05T06:53:46","slug":"simple-php-pdo-login-system-tutorial","status":"publish","type":"post","link":"https:\/\/woohuiren.me\/blog\/simple-php-pdo-login-system-tutorial\/","title":{"rendered":"Simple PHP PDO Login System Tutorial"},"content":{"rendered":"<p>Hello everyone,<\/p>\n<p>This is tutorial on making a simple PHP PDO login and registration system. We will be touching on PHP PDO, database design and security practices. It will be a bit different from the common tutorials out there as this will only cover certain core concepts and I will then give a final full example that will allow you to study from the code itself. This is because I find that having learnt core concepts and thereafter, learning from examples will make it easier for people to understand it better.<\/p>\n<h2>Introduction<\/h2>\n<p>So first off, here&#8217;s an introduction to PDO:<\/p>\n<p>PDO stands for PHP Data Objects, it is an interface for programmers to safely access databases in PHP. It is a better alternative to PHP MySQL, which is already deprecated, that has many new features such as statement preparation and object-oriented functions. This makes it quite different from PHP MySQL and PHP MySQLi.<\/p>\n<p>To start a database connection, you will need to call a new PDO object.<\/p>\n<blockquote><p><code>try {<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>$dbh = new PDO('mysql:host=localhost;dbname=some_database_name', 'admin_2', '123456'); \/\/establish it<\/code><\/p>\n<p><code>} catch (PDOException $e) {<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>\/\/seems like that there was an error!<\/code><br \/>\n<code>print_r(\"MySQL Error!: \" . $e-&gt;getMessage()); \/\/print the error output<\/code><br \/>\n<code>exit();<\/code><\/p>\n<p><code>}<\/code><\/p><\/blockquote>\n<p>If that work well for you, great, it confirms that you have PHP PDO!<\/p>\n<p>So next would be learning how to carry out a SELECT SQL statement.<\/p>\n<blockquote><p><code>$stmt = $dbh-&gt;prepare(\"SELECT * FROM `userdata` where `username` = ? AND `password` = ?\");<\/code><br \/>\n<code>$username = 'lol';<\/code><br \/>\n<code>$password = 'verysecuredpassword';<\/code><br \/>\n<code>$stmt-&gt;execute(array($username, $password);<\/code><\/p><\/blockquote>\n<p>Excellent! As you can see here, you prepare the SQL statement and when data needs to be specified, you will see this strange thing here &#8211; <code>where `username` = ? AND `password` = ?<\/code>. Very odd isn&#8217;t it? Its basically preparing this statement for the data to be called as you can see here &#8211; <code>$stmt-&gt;execute(array($username, $password);<\/code>. So the 1st question mark will reference to <code>$username<\/code> while the 2nd question mark will reference to <code>$password<\/code>. This would mean that the code is literally being executed as <code>\"SELECT * FROM `userdata` where `username` = \" . $username . \" AND `password` = \" . $password<\/code>.<\/p>\n<p>So far so good? Easy isn&#8217;t it? :)<\/p>\n<h2>Database Structure<\/h2>\n<p>Now that we have covered the PDO part, let us move on to database structure planning.<br \/>\nTo have a login system, we will need at least 3 things: PRIMARY <strong>id<\/strong> KEY, <strong>username<\/strong> and <strong>password<\/strong>. The username will be used to create an identity for the user while the password is used by the user to authenticate to the specified user account.<\/p>\n<p>So we should have something like this:<\/p>\n<blockquote><p><code>CREATE TABLE userdata (<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>id int(10) unsigned NOT NULL AUTO_INCREMENT,<\/code><br \/>\n<code>username varchar(64) NOT NULL,<\/code><br \/>\n<code>password varchar(64) NOT NULL<\/code><br \/>\n<code>PRIMARY KEY (id)<\/code><\/p>\n<p><code>) ENGINE=InnoDB DEFAULT CHARSET=utf8;<\/code><\/p><\/blockquote>\n<h2>Security Practices<\/h2>\n<p>Now that we are done with the database design, let&#8217;s move on to some security practices that are worth taking note of.<\/p>\n<p>Data sensitive details such as password should never be stored in plaintext in the database. Therefore, we should always hash these details using the PHP <code>crypt()<\/code> function. So this is what should be done:<\/p>\n<blockquote><p><code>$password = crypt($_REQUEST['password'], CRYPT_BLOWFISH);<\/code><\/p><\/blockquote>\n<p>Some people will try to attempt to insert script using HTML characters, we need to filter this off as well.<\/p>\n<blockquote><p><code>$username = htmlspecialchars($_REQUEST['username'], ENT_QUOTES, \"UTF-8\");<\/code><\/p><\/blockquote>\n<p>Although this HTML sanitisation is not required for passwords (since they are going to be encrypted anyway), we should just sanitise the password anyway.<\/p>\n<p>Next, we want to prevent identity hijacking in the event where people modify their cookies. So we need to create 2 cookies, one that contains the username and the other one contains a cookie hash. The cookie hash is used to verify the identity of the person and is unique upon every login. So even if a hijacker has modified their cookie to &#8220;GIANT_CRAB&#8221;, without the correct cookie hash, the system will reject him. This would also mean that we will need to modify our existing database design to include this new cookie hash.<\/p>\n<p>New database design:<\/p>\n<blockquote><p><code>CREATE TABLE userdata (<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>id int(10) unsigned NOT NULL AUTO_INCREMENT,<\/code><br \/>\n<code>username varchar(64) NOT NULL,<\/code><br \/>\n<code>password varchar(64) NOT NULL<\/code><br \/>\n<code>cookiehash varchar(64) DEFAULT NULL<\/code><br \/>\n<code>PRIMARY KEY (id)<\/code><\/p>\n<p><code>) ENGINE=InnoDB DEFAULT CHARSET=utf8;<\/code><\/p><\/blockquote>\n<p>Cookies that will be set:<\/p>\n<blockquote><p><code>setcookie('username_cookie', \"GIANT_CRAB\", 0, \"\/\", \"\", false, true);<\/code><br \/>\n<code>setcookie('login_cookie', \"abcd12345\", 0, \"\/\", \"\", false, true);<\/code><\/p><\/blockquote>\n<p>Identifying if a user is logged in, using both the username cookie and cookie hash:<\/p>\n<blockquote><p><code>\/\/$dbh parameter should be the database connection that is started using \"$dbh = new PDO();\"<\/code><br \/>\n<code>\/\/$username parameter should be the username of the user<\/code><br \/>\n<code>\/\/$cookiehash paramter should be the cookie hash value<\/code><br \/>\n<code>function isLoggedIn($dbh, $username, $cookiehash) {<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>$stmt = $dbh-&gt;prepare(\"SELECT * FROM `userdata` where `username` = ? AND `cookiehash` = ?\");<\/code><br \/>\n<code>if($stmt-&gt;execute(array($username, $cookiehash))) {<\/code><\/p>\n<p style=\"padding-left: 60px;\"><code>if($stmt-&gt;rowCount()) { \/\/check if user exists and the cookiehash is correct<\/code><\/p>\n<p style=\"padding-left: 90px;\"><code>return true;<\/code><\/p>\n<p style=\"padding-left: 60px;\"><code>}<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>} else {<\/code><\/p>\n<p style=\"padding-left: 60px;\"><code>return false;<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>}<\/code><\/p>\n<p><code>}<\/code><br \/>\n<code>\/\/so to check if a user is logged in, it is done like this<\/code><br \/>\n<code>if(isLoggedIn($dbh, $username, $cookiehash)) {<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>\/\/user is logged in<\/code><\/p>\n<p><code>} else {<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>\/\/user is not logged in or the cookie hash is incorrect<\/code><\/p>\n<p><code>}<\/code><\/p><\/blockquote>\n<p>Finally, we want to prevent cross site request forgery (CSRF) attempts too. This means that we need to generate a cookie and also modify the login form in such a way that it will also submit the value of the cookie.<\/p>\n<p>PHP:<\/p>\n<blockquote><p><code>$csrf_token = md5(mt_rand() . mt_rand()); \/\/we want it fast, security isn't a major issue here<\/code><br \/>\n<code>setcookie(\"csrf_token\", $csrf_token, 0, \"\/\", \"\", $ssl, true);<\/code><\/p><\/blockquote>\n<p>HTML:<\/p>\n<blockquote><p><code>&lt;form&gt;<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>&lt;div class=\"form-group\"&gt;<\/code><\/p>\n<p style=\"padding-left: 60px;\"><code>&lt;label for=\"loginUsername\"&gt;Username&lt;\/label&gt;<\/code><br \/>\n<code>&lt;input type=\"text\" class=\"form-control\" id=\"loginUsername\" placeholder=\"Enter username\"&gt;<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>&lt;\/div&gt;<\/code><br \/>\n<code>&lt;div class=\"form-group\"&gt;<\/code><\/p>\n<p style=\"padding-left: 60px;\"><code>&lt;label for=\"loginPassword\"&gt;Password&lt;\/label&gt;<\/code><br \/>\n<code>&lt;input type=\"password\" class=\"form-control\" id=\"loginPassword\" placeholder=\"Password\"&gt;<\/code><\/p>\n<p style=\"padding-left: 30px;\"><code>&lt;\/div&gt;<\/code><br \/>\n<code>&lt;input id=\"loginToken\" type=\"hidden\" value=\"&lt;?php echo $csrf_token; ?&gt;\"\/&gt;<\/code><br \/>\n<code>&lt;button type=\"submit\" class=\"btn btn-primary\"&gt;Login&lt;\/button&gt;<\/code><\/p>\n<p><code>&lt;\/form&gt;<\/code><\/p><\/blockquote>\n<h2>Conclusion<\/h2>\n<p>Congratulations! Now you have grabbed hold of the basic important concepts that are needed in handling a PHP login form. I have created a complete PHP PDO example that is available to download on Github, the URL will provided below.<\/p>\n<p>Github Example: <a href=\"https:\/\/github.com\/GIANTCRAB\/Simple-PHP-PDO-Login\">https:\/\/github.com\/GIANTCRAB\/Simple-PHP-PDO-Login<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hello everyone, This is tutorial on making a simple PHP PDO login and registration system. We will be touching on PHP PDO, database design and security practices. It will be a bit different from the common tutorials out there as this will only cover certain core concepts and I will then give a final full &hellip; <a href=\"https:\/\/woohuiren.me\/blog\/simple-php-pdo-login-system-tutorial\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Simple PHP PDO Login System Tutorial&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[5,29],"tags":[30],"class_list":["post-42","post","type-post","status-publish","format-standard","hentry","category-programming","category-tutorials","tag-php"],"jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/woohuiren.me\/blog\/wp-json\/wp\/v2\/posts\/42"}],"collection":[{"href":"https:\/\/woohuiren.me\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/woohuiren.me\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/woohuiren.me\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/woohuiren.me\/blog\/wp-json\/wp\/v2\/comments?post=42"}],"version-history":[{"count":3,"href":"https:\/\/woohuiren.me\/blog\/wp-json\/wp\/v2\/posts\/42\/revisions"}],"predecessor-version":[{"id":45,"href":"https:\/\/woohuiren.me\/blog\/wp-json\/wp\/v2\/posts\/42\/revisions\/45"}],"wp:attachment":[{"href":"https:\/\/woohuiren.me\/blog\/wp-json\/wp\/v2\/media?parent=42"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/woohuiren.me\/blog\/wp-json\/wp\/v2\/categories?post=42"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/woohuiren.me\/blog\/wp-json\/wp\/v2\/tags?post=42"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}