Membuat Script Login yang Aman dengan PHP dan MySQL


Sebuah aplikasi berbasis web umumnya membutuhkan halaman login bagi user/member untuk mengakses halaman tertentu. Halaman itu tentunya harus aman dari resiko pembobolan dan pencurian data.

Kali ini saya akan mencoba memberikan sedikit tips cara membuat script login yang aman dengan PHP dan MySQL.
Script ini hanya memberikan gambaran tentang bagaimana cara mengamankan halaman login dari ancaman dan resiko pembobolan dan pencurian data user/member. Script ini juga menggunakan teknik enkripsi password dengan fungsi hash(sha512) dan salt.
Saya tidak meng-klaim script ini benar-benar dapat membuat halaman login yang aman karena masalah keamanan system web tentu lebih luas dan kompleks.
Semoga tips berikut ini bisa menambah pengetahuan Anda dalam mengamankan aplikasi web Anda.

Script Login berikut ini diharapkan dapat menahan halaman web Anda dari serangan:
SQL Injections
Session Hijacking
Network Sniffing
Cross Site Scripting
Brute Force Attacks

Script ini jalan di PHP minimal versi 5.4 dan MySQL versi 5.0

Pertama kita siapkan databasenya.

1. Create Database login:

CREATE DATABASE `login`;

2. Create user untuk akses database tersebut. Hindari menggunakan user default root MySql.

CREATE USER 'admin_db'@'localhost' IDENTIFIED BY 'eKcGZr59zAa2BEWU';
GRANT SELECT, INSERT, UPDATE ON `login`.* TO 'sec_user'@'localhost';

3. Create tabel login_attempts untuk menyimpan data percobaan login oleh user. Dengan demikian kita bisa menahan serangan brute force attack

CREATE TABLE `login_attempts` (
    `user_id` INT(11) NOT NULL,
    `time` VARCHAR(30) NOT NULL
) ENGINE=InnoDB

4. Create tabel members dengan 5 field: id, username, email, password dan salt.
Khusus pada field password kita gunakan tipe CHAR(128) untuk menyimpan data password yang di encrypt dengan fungsi hash(sha512) dan salt .

CREATE TABLE `secure_login`.`members` (
    `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `username` VARCHAR(30) NOT NULL,
    `email` VARCHAR(50) NOT NULL,
    `password` CHAR(128) NOT NULL,
    `salt` CHAR(128) NOT NULL 
) ENGINE = InnoDB;

5. Insert 1 row data user admin ke tabel members untuk test login.

INSERT INTO `login`.`members` VALUES(1, 'admin_user', 'admin@kampushendra.com',
'00807432eae173f652f2064bdca1b61b290b52d40e429a7d295d76a71084aa96c0233b82f1feac45529e0726559645acaed6f3ae58a286b9f075916ebf66cacc',
'f9aab579fc1b41ed0c44fe4ecdbfcdb4cb99b9023abb241a6db833288f4eea3c02f76e0d35204a8695077dcf81932aa59006423976224be0390395bae152d4ef');

Berikutnya kita siapkan script PHP-nya.

1. Create folder “includes” di luar folder root aplikasi web Anda (misal: xampp/apps/includes/)

2. Create file psl-config.php berikut ini lalu simpan di folder includes:

<?php
/** These are the database login details */  
define("HOST", "localhost");    // The host you want to connect to.
define("USER", "admin_db");    // The database username. 
define("PASSWORD", "eKcGZr59zAa2BEWU");  // The database password. 
define("DATABASE", "login");     // The database name.
define("CAN_REGISTER", "any");
define("DEFAULT_ROLE", "member");
define("SECURE", FALSE);    // FALSE for Dev, TRUE for Prod using https
?>

3. Create file db_connect.php berikut ini lalu simpan di folder includes:

<?php
include_once 'psl-config.php';   // As functions.php is not included
$mysqli = new mysqli(HOST, USER, PASSWORD, DATABASE);

4. Create file functions.php berikut ini lalu simpan di folder includes:

<?php
include_once 'psl-config.php';
function sec_session_start() {
    $session_name = 'sec_session_id';   // Set a custom session name
    $secure = SECURE;
    // This stops JavaScript being able to access the session id.
    $httponly = true;
    // Forces sessions to only use cookies.
    if (ini_set('session.use_only_cookies', 1) === FALSE) {
        header("Location: ../error.php?err=Could not initiate a safe session (ini_set)");
        exit();
    }
    // Gets current cookies params.
    $cookieParams = session_get_cookie_params();
    session_set_cookie_params($cookieParams["lifetime"],
        $cookieParams["path"], 
        $cookieParams["domain"], 
        $secure,
        $httponly);
    // Sets the session name to the one set above.
    session_name($session_name);
    session_start();              // Start the PHP session 
    session_regenerate_id(true);  // regenerated the session, delete the old one. 
}

//This login function will check the email and password against the database. 
//It will return true if there is a match.
function login($email, $password, $mysqli) {
    // Using prepared statements means that SQL injection is not possible. 
    if ($stmt = $mysqli->prepare("SELECT id, username, password, salt 
        FROM members  WHERE email = ?  LIMIT 1")) {
        $stmt->bind_param('s', $email);  // Bind "$email" to parameter.
        $stmt->execute();    // Execute the prepared query.
        $stmt->store_result();
        // get variables from result.
        $stmt->bind_result($user_id, $username, $db_password, $salt);
        $stmt->fetch();
 
        // hash the password with the unique salt.
        $password = hash('sha512', $password . $salt);
        if ($stmt->num_rows == 1) {
            // If the user exists we check if the account is locked
            // from too many login attempts 
 
            if (checkbrute($user_id, $mysqli) == true) {
                // Account is locked 
                // Send an email to user saying their account is locked
                return false;
            } else {
                // Check if the password in the database matches
                // the password the user submitted.
                if ($db_password == $password) {
                    // Password is correct!
                    // Get the user-agent string of the user.
                    $user_browser = $_SERVER['HTTP_USER_AGENT'];
                    // XSS protection as we might print this value
                    $user_id = preg_replace("/[^0-9]+/", "", $user_id);
                    $_SESSION['user_id'] = $user_id;
                    // XSS protection as we might print this value
                    $username = preg_replace("/[^a-zA-Z0-9_\-]+/", 
                                                                "", 
                                                                $username);
                    $_SESSION['username'] = $username;
                    $_SESSION['login_string'] = hash('sha512', 
                              $password . $user_browser);
                    // Login successful.
                    return true;
                } else {
                    // Password is not correct
                    // We record this attempt in the database
                    $now = time();
                    $mysqli->query("INSERT INTO login_attempts(user_id, time)
                                    VALUES ('$user_id', '$now')");
                    return false;
                }
            }
        } else {
            // No user exists.
            return false;
        }
    }
}

//This checkbrute function will hold login tries for 2 hour if 5 logins failed.
function checkbrute($user_id, $mysqli) {
    // Get timestamp of current time 
    $now = time();
     // All login attempts are counted from the past 2 hours. 
    $valid_attempts = $now - (2 * 60 * 60);
     if ($stmt = $mysqli->prepare("SELECT time 
                             FROM login_attempts 
                             WHERE user_id = ? 
                            AND time > '$valid_attempts'")) {
        $stmt->bind_param('i', $user_id);
         // Execute the prepared query. 
        $stmt->execute();
        $stmt->store_result();
         // If there have been more than 5 failed logins 
        if ($stmt->num_rows > 5) {
            return true;
        } else {
            return false;
        }
    }
}
//This login_check function will Check if all session variables are set
function login_check($mysqli) {
    if (isset($_SESSION['user_id'], $_SESSION['username'], $_SESSION['login_string'])) {
        $user_id = $_SESSION['user_id'];
        $login_string = $_SESSION['login_string'];
        $username = $_SESSION['username'];
         // Get the user-agent string of the user.
        $user_browser = $_SERVER['HTTP_USER_AGENT'];
         if ($stmt = $mysqli->prepare("SELECT password FROM members WHERE id = ? LIMIT 1")) {
            // Bind "$user_id" to parameter. 
            $stmt->bind_param('i', $user_id);
            $stmt->execute();   // Execute the prepared query.
            $stmt->store_result();
             if ($stmt->num_rows == 1) {
                // If the user exists get variables from result.
                $stmt->bind_result($password);
                $stmt->fetch();
                $login_check = hash('sha512', $password . $user_browser);
                if ($login_check == $login_string) {
                    // Logged In!!!! 
                    return true;
                } else {
                    // Not logged in 
                    return false;
                }
            } else {
                // Not logged in 
                return false;
            }
        } else {
            // Not logged in 
            return false;
        }
    } else {
        // Not logged in 
        return false;
    }
}
//This login_check function will sanitizes URL from the PHP_SELF server variable
function esc_url($url) {
     if ('' == $url) {
        return $url;
    }
    $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i', '', $url);
    $strip = array('%0d', '%0a', '%0D', '%0A');
    $url = (string) $url;
     $count = 1;
    while ($count) {
        $url = str_replace($strip, '', $url, $count);
    }
    $url = str_replace(';//', '://', $url);
    $url = htmlentities($url);
    $url = str_replace('&amp;', '&', $url);
    $url = str_replace("'", ''', $url);
    if ($url[0] !== '/') {
        // We're only interested in relative links from $_SERVER['PHP_SELF']
        return '';
    } else {
        return $url;
    }
}

5. Create file process_login.php berikut ini lalu simpan di folder aplikasi web Anda (Misal: //xampp/htdocs/myweb/)

<?php
include_once 'db_connect.php';
include_once 'functions.php';
sec_session_start(); // custom secure way of starting a PHP session.
if (isset($_POST['email'], $_POST['p'])) {
    $email = $_POST['email'];
    $password = $_POST['p']; // The hashed password.
    if (login($email, $password, $mysqli) == true) {
        // Login success 
        header('Location: ../protected_page.php');
    } else {
        // Login failed 
        header('Location: ../index.php?error=1');
    }
} else {
    // The correct POST variables were not sent to this page. 
    echo 'Invalid Request';
}

6. Create file logout.php berikut ini lalu simpan di folder aplikasi web Anda (Misal: //xampp/htdocs/myweb/)

<?php
include_once 'functions.php';
sec_session_start();
// Unset all session values 
$_SESSION = array();
// get session parameters 
$params = session_get_cookie_params();
// Delete the actual cookie. 
setcookie(session_name(),'', time() - 42000,
        $params["path"], 
        $params["domain"], 
        $params["secure"], 
        $params["httponly"]);
// Destroy session 
session_destroy();
header('Location: ../index.php');

7. Create file index.php berikut ini lalu simpan di folder aplikasi web Anda (Misal: //xampp/htdocs/myweb/)
File index.php adalah halaman login Anda.

<?php
include_once 'includes/db_connect.php';
include_once 'includes/functions.php';
sec_session_start();
if (login_check($mysqli) == true) {
    $logged = 'in';
} else {
    $logged = 'out';
}
?>
<!DOCTYPE html>
<html>
<head>
    <title>Secure Login: Log In</title>
    <script type="text/JavaScript" src="js/sha512.js"></script> 
    <script type="text/JavaScript" src="js/forms.js"></script> 
</head>
<body>
        <?php
        if (isset($_GET['error'])) {
            if(@$_SESSION['error']==2)
		echo '<p class="error">Account is locked due to 5 failed logins!</p>';
	    else 
		echo '<p class="error">Error Logging In!</p>';
        }
        ?> 
        <form action="includes/process_login.php" method="post" name="login_form">                      
            Email: <input type="text" name="email" />
            Password: <input type="password" name="password" id="password"/>
            <input type="button" value="Login" 
                   onclick="formhash(this.form, this.form.password);" /> 
        </form>
<?php
        if (login_check($mysqli) == true) {
            echo '<p>Currently logged ' . $logged . ' as ' . htmlentities($_SESSION['username']) . '.</p>';
 
            echo '<p>Do you want to change user? <a href="includes/logout.php">Log out</a>.</p>';
        } else {
            echo '<p>Currently logged ' . $logged . '.</p>';
            echo "<p>If you don't have a login, please <a href='register.php'>register</a></p>";
                }
?>      
</body>
</html>

8. Create file protected_page.php berikut ini lalu simpan di folder aplikasi web Anda
File protected_page.php adalah halaman member yang bisa diakses setelah berhasil login.

<?php
include_once 'includes/db_connect.php';
include_once 'includes/functions.php';
sec_session_start(); 
if(login_check($mysqli) == false) {
    echo 'You are not authorized to access this page, please login.';
    exit();
}

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Secure Login: Protected Page</title>
        <link rel="stylesheet" href="styles/main.css" />
    </head>
    <body>
        <?php if (login_check($mysqli) == true) : ?>
            <p>Welcome <?php echo htmlentities($_SESSION['username']); ?>!</p>
            <p>
                This is an example protected page.  To access this page, users
                must be logged in.  At some stage, we'll also check the role of
                the user, so pages will be able to determine the type of user
                authorised to access the page.
            </p>
            <p>Return to <a href="index.php">login page</a></p>
        <?php else : ?>
            <p>
                <span class="error">You are not authorized to access this page.</span> 
Please <a href="index.php">login</a>.
            </p>
        <?php endif; ?>
    </body>
</html>

9. Create folder js di dalam folder aplikasi web Anda (Misal: //xampp/htdocs/myweb/js/)

10. Download file sha512.js dan forms.js lalu simpan di folder js

Setelah semua file siap, Anda dapat mencoba script login ini di web browser Anda.

Untuk script selengkapnya bisa Anda download dari sumber berikut ini:
https://github.com/peredurabefrog/phpSecureLogin

Leave your comment