· 9 years ago · Jun 02, 2017, 01:24 AM
1Step 1
2
3Let's configure our MySQL server.
4
51. Create a database named secure_login
6
72. Create a user with select, update, and insert privileges.
8Ex. User: 123secure_server
9Password: Abc!2#
10
113. Create a table named 'members' with five (5) fields: id, username, email, password, salt
12
13Code:
14CREATE TABLE `secure_login`.`members` (
15 `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
16 `username` VARCHAR(30) NOT NULL,
17 `email` VARCHAR(50) NOT NULL,
18 `password` CHAR(128) NOT NULL,
19 `salt` CHAR(128) NOT NULL
20) ENGINE = InnoDB;
21
224. Make a table to store login attempts.
23
24Code:
25CREATE TABLE `secure_login`.`login_attempts` (
26 `user_id` INT(11) NOT NULL,
27 `time` VARCHAR(30) NOT NULL
28) ENGINE=InnoDB
29
30Step 2
31
32Here we will create database connection pages so your login pages will easily connect with your MySQL server.
33
341. Let's create a 'includes' folder in your directory. Now create a PHP file called psl-config.php. In that file put this
35
36Code:
37<?php
38define("HOST", "localhost"); // The host you want to connect to.
39define("USER", "USER"); // The database username.
40define("PASSWORD", "PASSWORD"); // The database password.
41define("DATABASE", "secure_login"); // The database name.
42
43define("CAN_REGISTER", "any");
44define("DEFAULT_ROLE", "member");
45
46define("SECURE", FALSE);
47?>
48
49The above file will contain global variables.
50
512. Create a database connection page. Make a new file called db_connect.php
52
53Code:
54<?php
55include_once 'psl-config.php'; // As functions.php is not included
56$mysqli = new mysqli(HOST, USER, PASSWORD, DATABASE);
57?>
58
59Step 3
60
61Now it's time to create the PHP functions, basically making it more secure.
62
631. Create a new file in the 'includes' directory called functions.php
64
65Code:
66<?php
67include_once 'psl-config.php';
68
69function sec_session_start() {
70 $session_name = 'sec_session_id'; // Set a custom session name
71 $secure = SECURE;
72 // This stops JavaScript being able to access the session id.
73 $httponly = true;
74 // Forces sessions to only use cookies.
75 if (ini_set('session.use_only_cookies', 1) === FALSE) {
76 header("Location: ../error.php?err=Could not initiate a safe session (ini_set)");
77 exit();
78 }
79 // Gets current cookies params.
80 $cookieParams = session_get_cookie_params();
81 session_set_cookie_params($cookieParams["lifetime"],
82 $cookieParams["path"],
83 $cookieParams["domain"],
84 $secure,
85 $httponly);
86 // Sets the session name to the one set above.
87 session_name($session_name);
88 session_start(); // Start the PHP session
89 session_regenerate_id(); // regenerated the session, delete the old one.
90}
91function login($email, $password, $mysqli) {
92 // Using prepared statements means that SQL injection is not possible.
93 if ($stmt = $mysqli->prepare("SELECT id, username, password, salt
94 FROM members
95 WHERE email = ?
96 LIMIT 1")) {
97 $stmt->bind_param('s', $email); // Bind "$email" to parameter.
98 $stmt->execute(); // Execute the prepared query.
99 $stmt->store_result();
100
101 // get variables from result.
102 $stmt->bind_result($user_id, $username, $db_password, $salt);
103 $stmt->fetch();
104
105 // hash the password with the unique salt.
106 $password = hash('sha512', $password . $salt);
107 if ($stmt->num_rows == 1) {
108 // If the user exists we check if the account is locked
109 // from too many login attempts
110
111 if (checkbrute($user_id, $mysqli) == true) {
112 // Account is locked
113 // Send an email to user saying their account is locked
114 return false;
115 } else {
116 // Check if the password in the database matches
117 // the password the user submitted.
118 if ($db_password == $password) {
119 // Password is correct!
120 // Get the user-agent string of the user.
121 $user_browser = $_SERVER['HTTP_USER_AGENT'];
122 // XSS protection as we might print this value
123 $user_id = preg_replace("/[^0-9]+/", "", $user_id);
124 $_SESSION['user_id'] = $user_id;
125 // XSS protection as we might print this value
126 $username = preg_replace("/[^a-zA-Z0-9_\-]+/",
127 "",
128 $username);
129 $_SESSION['username'] = $username;
130 $_SESSION['login_string'] = hash('sha512',
131 $password . $user_browser);
132 // Login successful.
133 return true;
134 } else {
135 // Password is not correct
136 // We record this attempt in the database
137 $now = time();
138 $mysqli->query("INSERT INTO login_attempts(user_id, time)
139 VALUES ('$user_id', '$now')");
140 return false;
141 }
142 }
143 } else {
144 // No user exists.
145 return false;
146 }
147 }
148}
149function checkbrute($user_id, $mysqli) {
150 // Get timestamp of current time
151 $now = time();
152
153 // All login attempts are counted from the past 2 hours.
154 $valid_attempts = $now - (2 * 60 * 60);
155
156 if ($stmt = $mysqli->prepare("SELECT time
157 FROM login_attempts
158 WHERE user_id = ?
159 AND time > '$valid_attempts'")) {
160 $stmt->bind_param('i', $user_id);
161
162 // Execute the prepared query.
163 $stmt->execute();
164 $stmt->store_result();
165
166 // If there have been more than 5 failed logins
167 if ($stmt->num_rows > 5) {
168 return true;
169 } else {
170 return false;
171 }
172 }
173}
174unction login_check($mysqli) {
175 // Check if all session variables are set
176 if (isset($_SESSION['user_id'],
177 $_SESSION['username'],
178 $_SESSION['login_string'])) {
179
180 $user_id = $_SESSION['user_id'];
181 $login_string = $_SESSION['login_string'];
182 $username = $_SESSION['username'];
183
184 // Get the user-agent string of the user.
185 $user_browser = $_SERVER['HTTP_USER_AGENT'];
186
187 if ($stmt = $mysqli->prepare("SELECT password
188 FROM members
189 WHERE id = ? LIMIT 1")) {
190 // Bind "$user_id" to parameter.
191 $stmt->bind_param('i', $user_id);
192 $stmt->execute(); // Execute the prepared query.
193 $stmt->store_result();
194
195 if ($stmt->num_rows == 1) {
196 // If the user exists get variables from result.
197 $stmt->bind_result($password);
198 $stmt->fetch();
199 $login_check = hash('sha512', $password . $user_browser);
200
201 if ($login_check == $login_string) {
202 // Logged In!!!!
203 return true;
204 } else {
205 // Not logged in
206 return false;
207 }
208 } else {
209 // Not logged in
210 return false;
211 }
212 } else {
213 // Not logged in
214 return false;
215 }
216 } else {
217 // Not logged in
218 return false;
219 }
220}
221function esc_url($url) {
222
223 if ('' == $url) {
224 return $url;
225 }
226
227 $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url);
228
229 $strip = array('%0d', '%0a', '%0D', '%0A');
230 $url = (string) $url;
231
232 $count = 1;
233 while ($count) {
234 $url = str_replace($strip, '', $url, $count);
235 }
236
237 $url = str_replace(';//', '://', $url);
238
239 $url = htmlentities($url);
240
241 $url = str_replace('&', '&', $url);
242 $url = str_replace("'", ''', $url);
243
244 if ($url[0] !== '/') {
245 // We're only interested in relative links from $_SERVER['PHP_SELF']
246 return '';
247 } else {
248 return $url;
249 }
250}
251<?
252
253Step 4
254
255Now we need to process the logins.
256
2571. Create a file in the includes folder called process_login.php.
258
259Code:
260<?php
261include_once 'db_connect.php';
262include_once 'functions.php';
263
264sec_session_start(); // Our custom secure way of starting a PHP session.
265
266if (isset($_POST['email'], $_POST['p'])) {
267 $email = $_POST['email'];
268 $password = $_POST['p']; // The hashed password.
269
270 if (login($email, $password, $mysqli) == true) {
271 // Login success
272 header('Location: ../protected_page.php');
273 } else {
274 // Login failed
275 header('Location: ../index.php?error=1');
276 }
277} else {
278 // The correct POST variables were not sent to this page.
279 echo 'Invalid Request';
280}
281
2822. Create a logout.php file
283
284Code:
285<?php
286include_once 'functions.php';
287sec_session_start();
288
289// Unset all session values
290$_SESSION = array();
291
292// get session parameters
293$params = session_get_cookie_params();
294
295// Delete the actual cookie.
296setcookie(session_name(),
297 '', time() - 42000,
298 $params["path"],
299 $params["domain"],
300 $params["secure"],
301 $params["httponly"]);
302
303// Destroy session
304session_destroy();
305header('Location: LOCATION YOU WANT IT TO GO');
306
3073. Now in your root folder create register.php
308
309Code:
310<?php
311include_once 'includes/register.inc.php';
312include_once 'includes/functions.php';
313?>
314<!DOCTYPE html>
315<html>
316 <head>
317 <meta charset="UTF-8">
318 <title>Secure Login: Registration Form</title>
319 <script type="text/JavaScript" src="js/sha512.js"></script>
320 <script type="text/JavaScript" src="js/forms.js"></script>
321 <link rel="stylesheet" href="styles/main.css" />
322 </head>
323 <body>
324 <!-- Registration form to be output if the POST variables are not
325 set or if the registration script caused an error. -->
326 <h1>Register with us</h1>
327 <?php
328 if (!empty($error_msg)) {
329 echo $error_msg;
330 }
331 ?>
332 <ul>
333 <li>Usernames may contain only digits, upper and lower case letters and underscores</li>
334 <li>Emails must have a valid email format</li>
335 <li>Passwords must be at least 6 characters long</li>
336 <li>Passwords must contain
337 <ul>
338 <li>At least one upper case letter (A..Z)</li>
339 <li>At least one lower case letter (a..z)</li>
340 <li>At least one number (0..9)</li>
341 </ul>
342 </li>
343 <li>Your password and confirmation must match exactly</li>
344 </ul>
345 <form action="<?php echo esc_url($_SERVER['PHP_SELF']); ?>"
346 method="post"
347 name="registration_form">
348 Username: <input type='text'
349 name='username'
350 id='username' /><br>
351 Email: <input type="text" name="email" id="email" /><br>
352 Password: <input type="password"
353 name="password"
354 id="password"/><br>
355 Confirm password: <input type="password"
356 name="confirmpwd"
357 id="confirmpwd" /><br>
358 <input type="button"
359 value="Register"
360 clickon="return regformhash(this.form,
361 this.form.username,
362 this.form.email,
363 this.form.password,
364 this.form.confirmpwd);" />
365 </form>
366 <p>Return to the <a href="index.php">login page</a>.</p>
367 </body>
368</html>
369
370This code embeds PHP in HTML, feel free to edit the style sheet stuff to your liking.
371
3724. In the includes folder create register.inc.php
373
374Code:
375<?php
376include_once 'db_connect.php';
377include_once 'psl-config.php';
378
379$error_msg = "";
380
381if (isset($_POST['username'], $_POST['email'], $_POST['p'])) {
382 // Sanitize and validate the data passed in
383 $username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
384 $email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
385 $email = filter_var($email, FILTER_VALIDATE_EMAIL);
386 if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
387 // Not a valid email
388 $error_msg .= '<p class="error">The email address you entered is not valid</p>';
389 }
390
391 $password = filter_input(INPUT_POST, 'p', FILTER_SANITIZE_STRING);
392 if (strlen($password) != 128) {
393 // The hashed pwd should be 128 characters long.
394 // If it's not, something really odd has happened
395 $error_msg .= '<p class="error">Invalid password configuration.</p>';
396 }
397
398 // Username validity and password validity have been checked client side.
399 // This should should be adequate as nobody gains any advantage from
400 // breaking these rules.
401 //
402
403 $prep_stmt = "SELECT id FROM members WHERE email = ? LIMIT 1";
404 $stmt = $mysqli->prepare($prep_stmt);
405
406 // check existing email
407 if ($stmt) {
408 $stmt->bind_param('s', $email);
409 $stmt->execute();
410 $stmt->store_result();
411
412 if ($stmt->num_rows == 1) {
413 // A user with this email address already exists
414 $error_msg .= '<p class="error">A user with this email address already exists.</p>';
415 $stmt->close();
416 }
417 $stmt->close();
418 } else {
419 $error_msg .= '<p class="error">Database error Line 39</p>';
420 $stmt->close();
421 }
422
423 // check existing username
424 $prep_stmt = "SELECT id FROM members WHERE username = ? LIMIT 1";
425 $stmt = $mysqli->prepare($prep_stmt);
426
427 if ($stmt) {
428 $stmt->bind_param('s', $username);
429 $stmt->execute();
430 $stmt->store_result();
431
432 if ($stmt->num_rows == 1) {
433 // A user with this username already exists
434 $error_msg .= '<p class="error">A user with this username already exists</p>';
435 $stmt->close();
436 }
437 $stmt->close();
438 } else {
439 $error_msg .= '<p class="error">Database error line 55</p>';
440 $stmt->close();
441 }
442
443 // TODO:
444 // We'll also have to account for the situation where the user doesn't have
445 // rights to do registration, by checking what type of user is attempting to
446 // perform the operation.
447
448 if (empty($error_msg)) {
449 // Create a random salt
450 //$random_salt = hash('sha512', uniqid(openssl_random_pseudo_bytes(16), TRUE)); // Did not work
451 $random_salt = hash('sha512', uniqid(mt_rand(1, mt_getrandmax()), true));
452
453 // Create salted password
454 $password = hash('sha512', $password . $random_salt);
455
456 // Insert the new user into the database
457 if ($insert_stmt = $mysqli->prepare("INSERT INTO members (username, email, password, salt) VALUES (?, ?, ?, ?)")) {
458 $insert_stmt->bind_param('ssss', $username, $email, $password, $random_salt);
459 // Execute the prepared query.
460 if (! $insert_stmt->execute()) {
461 header('Location: ../error.php?err=Registration failure: INSERT');
462 }
463 }
464 header('Location: ./register_success.php');
465 }
466}
467
468Step 5
469
470Java Script!
471
4721. Create a folder named 'js' in your root directory.
473
4742. Create a file named sha512.js, this will encrypt our shit.
475
476Here's the code: http://pajhome.org.uk/crypt/md5/sha512.html
477
4783. Create forms.js, this will handle hashing of registration and login.
479
480Code:
481function formhash(form, password) {
482 // Create a new element input, this will be our hashed password field.
483 var p = document.createElement("input");
484
485 // Add the new element to our form.
486 form.appendChild(p);
487 p.name = "p";
488 p.type = "hidden";
489 p.value = hex_sha512(password.value);
490
491 // Make sure the plaintext password doesn't get sent.
492 password.value = "";
493
494 // Finally submit the form.
495 form.submit();
496}
497
498function regformhash(form, uid, email, password, conf) {
499 // Check each field has a value
500 if (uid.value == '' ||
501 email.value == '' ||
502 password.value == '' ||
503 conf.value == '') {
504
505 alert('You must provide all the requested details. Please try again');
506 return false;
507 }
508
509 // Check the username
510
511 re = /^\w+$/;
512 if(!re.test(form.username.value)) {
513 alert("Username must contain only letters, numbers and underscores. Please try again");
514 form.username.focus();
515 return false;
516 }
517
518 // Check that the password is sufficiently long (min 6 chars)
519 // The check is duplicated below, but this is included to give more
520 // specific guidance to the user
521 if (password.value.length < 6) {
522 alert('Passwords must be at least 6 characters long. Please try again');
523 form.password.focus();
524 return false;
525 }
526
527 // At least one number, one lowercase and one uppercase letter
528 // At least six characters
529
530 var re = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}/;
531 if (!re.test(password.value)) {
532 alert('Passwords must contain at least one number, one lowercase and one uppercase letter. Please try again');
533 return false;
534 }
535
536 // Check password and confirmation are the same
537 if (password.value != conf.value) {
538 alert('Your password and confirmation do not match. Please try again');
539 form.password.focus();
540 return false;
541 }
542
543 // Create a new element input, this will be our hashed password field.
544 var p = document.createElement("input");
545
546 // Add the new element to our form.
547 form.appendChild(p);
548 p.name = "p";
549 p.type = "hidden";
550 p.value = hex_sha512(password.value);
551
552 // Make sure the plaintext password doesn't get sent.
553 password.value = "";
554 conf.value = "";
555
556 // Finally submit the form.
557 form.submit();
558 return true;
559}
560
561Step 6
562
563HTML! Now you make the pages, fun!
564
5651. Lets create the login form, name it login.php and keep it in the root directory.
566
567Code:
568<?php
569include_once 'includes/db_connect.php';
570include_once 'includes/functions.php';
571
572sec_session_start();
573
574if (login_check($mysqli) == true) {
575 $logged = 'in';
576} else {
577 $logged = 'out';
578}
579?>
580<!DOCTYPE html>
581<html>
582 <head>
583 <title>Secure Login: Log In</title>
584 <link rel="stylesheet" href="styles/main.css" />
585 <script type="text/JavaScript" src="js/sha512.js"></script>
586 <script type="text/JavaScript" src="js/forms.js"></script>
587 </head>
588 <body>
589 <?php
590 if (isset($_GET['error'])) {
591 echo '<p class="error">Error Logging In!</p>';
592 }
593 ?>
594 <form action="includes/process_login.php" method="post" name="login_form">
595 Email: <input type="text" name="email" />
596 Password: <input type="password"
597 name="password"
598 id="password"/>
599 <input type="button"
600 value="Login"
601 clickon="formhash(this.form, this.form.password);" />
602 </form>
603 <p>If you don't have a login, please <a href="register.php">register</a></p>
604 <p>If you are done, please <a href="includes/logout.php">log out</a>.</p>
605 <p>You are currently logged <?php echo $logged ?>.</p>
606 </body>
607</html>
608
6092. Create register_success.php
610
611Code:
612<!DOCTYPE html>
613<html>
614 <head>
615 <meta charset="UTF-8">
616 <title>Secure Login: Registration Success</title>
617 <link rel="stylesheet" href="styles/main.css" />
618 </head>
619 <body>
620 <h1>Registration successful!</h1>
621 <p>You can now go back to the <a href="index.php">login page</a> and log in</p>
622 </body>
623</html>
624
6253. Create error.php
626
627Code:
628<?php
629$error = filter_input(INPUT_GET, 'err', $filter = FILTER_SANITIZE_STRING);
630
631if (! $error) {
632 $error = 'Oops! An unknown error happened, try again.';
633}
634?>
635<!DOCTYPE html>
636<html>
637 <head>
638 <meta charset="UTF-8">
639 <title>Secure Login: Error</title>
640 <link rel="stylesheet" href="styles/main.css" />
641 </head>
642 <body>
643 <h1>There was a problem</h1>
644 <p class="error"><?php echo $error; ?></p>
645 </body>
646</html>
647
648Note, the above code is HIGHLY customizable.
649
650
651Conclusion
652
653You now should have a very secure login/registration script for your site!
654
655If you have any questions, PM me or post here.
656
657Also note, not all scripts were made by me.