What is the best way to create a PHP login system

Asked

Viewed 32,253 times

106

I’m making a small system, and to access it, the user enters the login and password.

What is the best way - safe and simple- to do the login and password system?

  • 2

    I recommend taking a look at this project https://github.com/panique/php-login-minimal . It is a very simple PHP authentication system with all good practices applied.

  • Boy, it’s a very broad question, but come on, you can use session, as discussed here https://answall.com/questions/3049/por-que-usar-sessions-nao-e-uma-alternativa-na-authentication/3242 or even use Oauth, with the Facebook or Google But I believe that to learn, you must create in hand an authentication management system like this tutorial

  • 8

    Can you give more information than you need, what you’ve done, what requirements? The answers already show how difficult it is to provide a real answer to the question.

  • how many users?? How is the user registration (only user and password?)... If it is one, or some that will never change again, just a fixed array, nor need database.

  • 1

    There are numerous correct answers to this question, so you must specify it so that only one answer is correct. Opinion should not be taken into account in a response, but facts.

  • 6

    Note to readers: this question has old and outdated answers, which do not use basic password storage tools such as password_hash and password_verify, among other problems. There are solutions that were valid at the time of posting, but that currently should not be applied as they are.

  • To better understand the problems, see this answer further below.

  • Do not use php... :p

Show 3 more comments

6 answers

97


For secure authentication it is necessary to identify several points.

Some items you should take care of:

  • SQL Injection
  • Password encryption
  • Brute force attack
  • XSS attacks
  • CSRF attacks
  • Protect session files
  • Protect files from systems

1 - Starting

Set a hash that will be used throughout your system. an example is

define( 'SECURITY_HASH', 'uma frase qualquer ou letras aleatórias com números e simbolos' )

2 - Create your table in the database

Table suggestion

id
email
username
password
keymaster
last_ip
last_access
active
created_at
uptaded_at

3 - User registration

When registering validate whether a valid email has been passed, if the username field is in the correct formatted one, and generate a hash for the keymaster and use this hash to encrypt the password.

To validate the email use

if ( filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
    echo "This ($email_a) email address is considered valid.";
}

An example of how to generate Hash

crypt( rand(99999) . time() .  $_SERVER["REMOTE_ADDR"], SECURITY_HASH );

A safer way to use this feature is to use some $2a$ at the beginning of the second parameter to identify the type of encryption that should be used. Thus:

crypt( rand(99999) . time() .  $_SERVER["REMOTE_ADDR"], '$2a$07$' . SECURITY_HASH );

Where $2a$ specifies the Blowfish algorithm and 07$ the cost to generate the hash.

or if you are using php 5.5 you can use the password_hash() function. Learn more in http://br1.php.net/manual/en/function.password-hash.php

$_SERVER["REMOTE_ADDR"] // Obtem o IP do usuário

Store the user’s IP and last-access dates, and when it was created.

Leave the user inactive (active field = 0 ) and validate the registered email by sending a confirmation email.

In the confirmation email you provide a link of the type

http://www.dominio.com.br/validateuser.php?key={hash do resultado da concatenação do keymaster e SECURITY_HASH}

This url will change the active field from 0 to 1, indicating that the user has been enabled.

4 - Login

For the login you must receive the form data and validate them, checking if you are as expected.

Search the user information in the bank, using only the given username.

With the data you obtained from the database, encrypt the password entered by the form using the user’s keymaster, the same registration procedure.

Compare the password you got from the bank with the password you received from the encrypted form If they are identical, log in.

5 - Basic care

To get data passed via POST or GET, avoid using pure global variables. For this use filter_input().

Read about in the documentation: http://www.php.net/manual/en/function.filter-input.php

--

To ensure that the data sent via form was carried out from your server, create a hash and store in session. Add this hash to a Hidden field in the form. When receiving form values, check if the hash that came from the form exists and is equal to the session.

Renew the hash whenever a form is displayed.

In this link you have a complete example about prevention of this type of attack https://www.owasp.org/index.php/PHP_CSRF_Guard

--

  • Never use the register_globals set with on
  • Do not validate your forms only in javascript, because if the user is with javascript disabled will pass directly.

  • Use PDO to validate all variables before passing them to an SQL.

  • Configure in php.ini the following Session.cookie_httponly = 1 . This informs the browser not to expose cookies for side client languages like javascript.
  • Name all files with extension . php never with extension like . inc, . conf, etc.
  • Avoid using md5 as encryption, use bcrypt because the algorithm is much safer.
  • Save the session in the database
  • Set a domain for your cookie
  • Count login attempts and the time between them to avoid robots.
  • In production leave display_errors as off
  • Generate error log and send it to your email, this makes it easy to identify hacking attempts.
  • Beware of the permissions of your files on the server, make sure only apache has permission on them.

Some useful links.

To implement the "remind me" I made a description on another answer here at Stackoverflow

  • 1

    -1: The answer presents a huge confusion of very important concepts in the area of information security (encryption algorithms vs hash algorithms) and guidelines that lead to unsafe systems ("crypt" function). Caution. I recommend participating in the course "Cryptography I" in the Coursera to improve the understanding of these concepts.

  • 1

    Hello Bruno, the guidance on the hash I indicate to explain the logic that the login system should have. Regarding the use of the crypt function, the possibility of using the password_hash() function is commented on below, and I also quote about the use of bcrypt in the list of basic care. The crypt function also makes it possible to simulate the Blowfish algorithm. Thank you for the course suggestion.

  • I have to agree with @Brunoreis. This false "hash" function is not appropriate. The recommendation of password_hash is corrreta, but the home function is not. The recommendation is password_hash if you have, http://www.openwall.com/phpass/ for older versions (in bcrypt mode, if possible).

  • I agree that the hash function crypt pure is not the most recommended, and I quote in the post on the items you should take care of. However this function allows you to work with Blowfish quietly the Thiago Belem made a class of example on the use, and in the documentation also talks about the possibility of using Blowfish using the function crypt in older php. So in my example I used this function as example password_hash as a recommendation.

  • 1

    @marcusagm, "not the most recommended" is an understatement. Using this hash function is actually a security breach (and not simply "something acceptable but different from the most recommended"). Simple as that. Something else: Blowfish is an algorithm of cryptography, and not a hash function, therefore Blowfish does not serve to store passwords safely. Using Blowfish to store passwords in a database is again a security breach.

  • 3

    Another thing: I took a quick look at the source code you refer to and, despite using a strong KDF, it contains several security holes (which is serious! after all, the article proposes to develop a method safe to store passwords; gives a false sense of security to the lay reader). Some examples of security failures, indeed subtle but serious: (1) uses mt_rand as PRNG - implements Mersenne Twister which is excellent for science, but is not criptographically Strong; (2) uses uniqid which again is not Crypto. Strong. Result: Blowfish + unsafe inputs = unsafe.

Show 1 more comment

46

The best authentication is non-authentication

Follow some recommendations:

Don’t invent the wheel

If possible use other sites to authenticate on yours. There is Openid technology that allows this, and it is very easy. An example of using Openid is the stackoverflow website itself: you can link your stackoverflow user to a Google or Facebook account for example. In this way you delegate the security of your site to other sites that - theoretically - are more reliable. At least login access security.

Never, ever, ever keep passwords

Save the user/password combination is the nightmare of any sysadmin and developer. The first option (Openid) is the safest. However, if Openid is not an option - let’s say it’s a site that’s on an Intranet that doesn’t have access to the Internet - the way is to save user and password. Thus, the best approach is to save the password in such a way that in case some type of database theft occurs (from the user table and passwords), you give work to the hacker when searching for the password. That means...

Never keep passwords in plain text

The first approach that everyone takes (I already did... in 2002!) is to keep the passwords in plain text. Take the test: sign up at any site and click on the "I forgot my password" option. What should happen? The site will send a message asking you to enter a specific URL, enter a new password, and this request will expire in x minutes or y hours. What nay should happen, under no circumstances, is the site send an email to you with the password you forgot. If this happened, put this site in a blacklist because it keeps the passwords in plain text. On the other hand...

If you’re gonna use hash, think again

First was the plain text. Now you’ve learned and want to "evolve". What do you do? Of course, you use a hash code generation algorithm to hide the password. Basically, a hash code algorithm takes a series of bytes, makes a very crazy account involving summations and or’s-unique, twists and twists and generates a code: the hash code referring to the series of bytes that was used as input. It is virtually impossible to infer the password just by looking at the generated hash code. It is a one-way routine, shall we say. "Perfect! I’ll use hash and get along!" It’s... just not. Let’s assume that you chose the MD5 algorithm to generate your hash code. Face you will have two problems:

  1. If the guy has a computer foot and a little time, he can "break the hash". What do you mean? Let’s assume the password you typed gave a hash code aaaaabbcc0011 (is just one example, the MD5 hash code has 32 hexadecimal digits). The hacker then uses a brute force algorithm that generates passwords until one of them gives the same hash code that the original password hash code. Detail: the password generated by the hacker can be totally different from the original password. This phenomenon is called collision, and in the case of MD5 it happens with some ease;

  2. But the real hacker knows that users don’t even care about passwords, so what does he do? He uses a "catalog of passwords". Want an example? The most common password on multiple sites is the infamous sequence 12345678. The hacker gets his users table and passwords and notes that several of them are like this: 25d55ad283aa400af464c76d713c07ad. He doesn’t even think twice: type as password 12345678 and be happy. On the Internet there are several catalogs with the hash code of the most common passwords. On account of that...

Throw a little salt to make it taste good

One way to further complicate the hacker’s work is to salt the hash generation. "How so?". It’s simple: you generate any random alphanumeric code and, when generating the hash, you include that code and preferably generate the hash again. Let us return to the example of 12345678. Let’s imagine that I generated a random salt like this 1adef56ghdfr43256yb. When I generate the MD5 code from 12345678, we’ll have that acquaintance of ours up there. Then I take our acquaintance, along with the salt and... I’ll take the MD5 again! Look at the result: 7fb7daece2240e3ab134d6d4f9fe29fd. And if I do it one more time, I get: 1d2e962f721cee69fb3bca0d5ce9394b. I can do this as many times as I want, as long as I repeat the procedure the moment the user tries to authenticate on the site. Some important remarks:

  1. The salt should be different for each user. If it is the same salt for all users, the generated code will be the same and then just the hacker find a dummy with password 12345678 that he (or she) finds the others;

  2. Salt needs to be stored in the database. Nothing prevents it from being in different tables, as long as it does not degrade performance;

  3. Each time the user changes the password, also change the salt. It is an extra guarantee;

Security depends on the password, and goes beyond it

Well, even with salt it is best not to abuse to avoid the evils of salt, you could, for example, use other hash algorithms such as SHA1 or SHA2 combined with salt. There is also the possibility to encrypt the password directly in the database, but this is usually not an option (mainly on shared hosts). So, since the way is to keep the password and keep it in the bank, here are the endpoints. If you do this, the chance of something happening will decrease greatly and your site will be safer beyond login:

Disable register_globals, for God’s sake!

Register_globals in PHP is the capeta, the tinhoso. Simple as that. How does it work? Let’s assume that your form has a field with name... name (sorry, my creativity ended up there). With register_globals disabled, to read the contents of this form field, you use $_GET['nome'] or $_POST['nome']. Now if register_globals is on, the PHP engine "magically" will create a variable called... $name! You can do very ugly things on websites with register_globals enabled. The default in PHP 5 is off, but it has sysop that has to reactivate it because of dummy websites that still use this feature. If your sysop is in a good mood, it will let you set up your application’s register_globals in the file. htaccess located in the root directory of your site (this runs a little beyond the scope of stackoverflow, but look at the stackexchange group sites you think).

Concatenate SQL with parameters: your hacker friend thanks you

When performing queries in BD always use methods and routines that support linked parameters (bound Parameters). If you are from PDO (recommend), it would be something like this:

$banco = new PDO(...);
$comando = $banco->prepare("SELECT ID, NOME FROM USUARIOS WHERE LOGIN = :login AND SENHA = :senha");

// Vamos considerar que você já pegou o sal daquele usuário e já aplicou os esquemas.
// Dá para fazer dentro do banco, se você preferir. É até melhor...

$comando->execute(array(':login' => $login, ':senha' => $senha_com_sal));

if ($comando->fetch()) { // Usuário logado!

} else { // Usuário incorreto ou a senha está incorreta!

}

Why is this approach better than simply concatenating the password and quotation marks? For the hacker, the concatenation allows a type of attack called "SQL Injection". Usually he does it in a way that he puts certain characters inside one of the fields (the name for example) and then he manages to turn an innocent SELECT that should bring 0 or 1 line, into a powerful SELECT that brings all the rows of the table. Depending on the hacker’s experience (and the programmer’s inexperience), he can even use Insert’s and update’s and detonate the site, or open it fully. Search for SQL Injection Attacks on the Internet and get ready for horror scenes!

One remark: this bound Parameters system also works with mysqli and the like. Search in the documentation. It’s worth a lot.

Technology does not win a good social engineering

As I said earlier, a lot of people (a lot of people) use passwords like 12345678, superman, etc. for the most diverse purposes. The best way to prevent attacks (or make them even harder) is to force the user to enter passwords with minimal security. Establish rules for users to create passwords. A few simple rules, but they can make all the difference: 1. The password shall be at least 8 characters long; 2. the password shall be at least 2 alphabetical characters (a-z); 3. the password must be at least 2 numeric characters (0-9); 4. the password must be at least 2 alphabetical characters in capital letters (A-Z); 5. The password must have at least 1 special character, like $&@#! % etc.

and so on.

Many of these tips I took from my experience and also from this video (in English): http://youtu.be/8ZtInClXe1Q

Good luck and tell us how it was.

  • 1

    MD5/SHA+sal is not enough because the raw force based on graphics cards can break them in it. The hash algorithm should be slow by nature.

  • 1

    @luiscubal could give some example of using GPGPU for raw strength of MD5 or SHA hashes with salt? Most of the examples I know use Rainbow Tables...

  • 3

    http://hashcat.net/oclhashcat/ Supports MD5, SHA1, HMAC-SHA1, SHA256, HMAC-SHA256, etc. with and without salt.

  • 1

    From what I understand this algorithm can break salt + password and vice versa. But I don’t know if I could break something like md5(salt + md5(password)) or md5(salt + md5(salt + md5(password)). Still, you can’t be too careful

  • Given what they support, changing the tool to support these also shouldn’t be difficult. md5($salt.md5($pass)) seems to be supported in 3710 mode. And supposedly people can ask them to add new formats.

20

With more and more reports of hacking activities in the news, developers are looking for the best alternatives to ensure greater security on their websites. If your site has a member system, it may be at risk such as being hacked and having the information of your users compromised. This guide will show you the best ways to create your user system and login in the most secure way possible. By following this guide, you can learn how to prevent various types of attacks that hackers may use to gain control of other users' accounts, delete accounts, and/or modify data.

Material Requirements

Since we will be using the PHP class set myqli_* to access our mysql database, you will need the following versions of PHP and mysql: PHP version 5 or higher Mysql version 4.1.3 or later To check the PHP and mysql version on your server, use the phpinfo function();.

Set Up Your Server

Install PHP and Mysql on your server. Most webhosts will have php and mysql already installed, so you just need to check that they are with the most current versions of each for this guide to work.

Configure the Mysql Database

1 Create a Mysql database. In this guide, you will create a database called "secure_login" (secure login). You can do this through Phpmyadmin or use the following SQL command to create it for you.

Code for database creation:

CREATE DATABASE `secure_login`;

Note: Some host services do not allow you to create databases through phpMyAdmin, in cases where you need to use cPanel.

2 Create a user with SELECT (selection), UPDATE (update) and INSERT (insertion) privileges only).

This means that if there is ever a security breach in your script, the hacker will not be able to delete or extract anything from our database. Using this user, you can do pretty much anything you want in your app. If you’re really paranoid, create a different user for each function.

  • User: "sec_user"
  • Password: "eKcGZr59zAa2BEWU"

Code for user creation:

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

Note: It is a good idea to modify the user password in the above code when running it on your own server. (Be sure to modify your PHP code as well.) Remember that it doesn’t have to be a password that you can remember, so make it as complicated as possible.

3 Create a Mysql table called "Members" (members).

The following code creates a table with 5 fields (id, username, email, password, salt).

Create the "Members table:

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;

We use the type of CHAR data for fields which we know the size (number of characters), such as the password (password) and "salt" fields which will always have a length of 128 characters. Using CHAR here, you’ll be saving processing.

4 Create a table to store login attempts.

We will use this table to store a user’s login attempts. This is how we will prevent attack attempts with force or brute force.

Create the table "login_attempts":

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

5 Create a test line in the "Members table".

Since we are not creating a sign-up page, it will be important that you are able to test your login script. Below is the script to create a user with these details:

Add the test user:

INSERT INTO `secure_login`.`members` VALUES(1, 'test_user', '[email protected]', '00807432eae173f652f2064bdca1b61b290b52d40e429a7d295d76a71084aa96c0233b82f1feac45529e0726559645acaed6f3ae58a286b9f075916ebf66cacc', 'f9aab579fc1b41ed0c44fe4ecdbfcdb4cb99b9023abb241a6db833288f4eea3c02f76e0d35204a8695077dcf81932aa59006423976224be0390395bae152d4ef');

Create a Database Connection Page

1 Create a database connection page.

This is the php code we will use to connect to the Mysql database. Create a new php file called "db_connect.php" and add the following code. You can then include the file on any page you wish to connect to the database.

Database connection (db_connect.php):

define("HOST", "localhost"); // O host no qual você deseja se conectar.
define("USER", "sec_user"); // O nome de usuário do banco de dados.
define("PASSWORD", "eKcGZr59zAa2BEWU"); // A senha do usuário do banco de dados. 
define("DATABASE", "secure_login"); // O nome do banco de dados.

$mysqli = new mysqli(HOST, USER, PASSWORD, DATABASE);
// Se você estiver se conectando via TCP/IP ao invés de um socket UNIX, lembre-se de adicionar o número da porta como um parâmetro.

Create PHP Functions

These functions will do all the processing of the login script. Add all the functions on a page called "functions.php".

1 Sign in a secure PHP session.

It’s important not to just put "session_start()"; on top of all pages where you want to use php sessions, if you’re really concerned about security, this is how you should do it. You will create a function called "sec_session_start", which will start a php session safely. You should call this function at the top of each page from which you wish to access a php session variable.

Secure Login Function:

function sec_session_start() {
        $session_name = 'sec_session_id'; // Define um nome padrão de sessão
        $secure = false; // Defina como true (verdadeiro) caso esteja utilizando https.
        $httponly = true; // Isto impede que o javascript seja capaz de acessar a id de sessão. 

        ini_set('session.use_only_cookies', 1); // Força as sessões a apenas utilizarem cookies. 
        $cookieParams = session_get_cookie_params(); // Recebe os parâmetros atuais dos cookies.
        session_set_cookie_params($cookieParams["lifetime"], $cookieParams["path"], $cookieParams["domain"], $secure, $httponly); 
        session_name($session_name); // Define o nome da sessão como sendo o acima definido.
        session_start(); // Inicia a sessão php.
        session_regenerate_id(true); // regenerada a sessão, deleta a outra.
}

This function makes your login script much more secure. Prevents hackers from being able to access the session id cookie through javascript (for example, in an XSS attack). It also uses the "session_regenerate_id()" function, which generates the session id again at each page update, helping to prevent a hijack attack in the session. Note: If you are using https in your login application, set the "$Secure" variable to "true".

2 Create a login function.

This function will check the email and password in the database and return a value "true" (true) if both are correct and matching.

Safe Login Function:

function login($email, $password, $mysqli) {
   // utilizar declarações preparadas significa que a injeção de código SQL não será possível. 
   if ($stmt = $mysqli->prepare("SELECT id, username, password, salt FROM members WHERE email = ? LIMIT 1")) { 
      $stmt->bind_param('s', $email); // Vincula "$email" ao parâmetro.
      $stmt->execute(); // Executa a query preparada.
      $stmt->store_result();
      $stmt->bind_result($user_id, $username, $db_password, $salt); // obtém variáveis do resultado.
      $stmt->fetch();
      $password = hash('sha512', $password.$salt); // confere o hash de "$password" e "$salt"
      if($stmt->num_rows == 1) { // se o usuário existe
         // Nós checamos se a conta está bloqueada devido a várias tentativas de login
         if(checkbrute($user_id, $mysqli) == true) { 
            // Conta está bloqueada
            // Envia um email ao usuário comunicando que sua conta foi bloqueada
            return false;
         } else {
         if($db_password == $password) { // Checa se a senha na base de dados confere com a senha que o usuário digitou. 
            // Senha está correta!

               $ip_address = $_SERVER['REMOTE_ADDR']; // Pega o endereço IP do usuário. 
               $user_browser = $_SERVER['HTTP_USER_AGENT']; // Pega a string de agente do usuário.

               $user_id = preg_replace("/[^0-9]+/", "", $user_id); // Proteção XSS conforme poderíamos imprimir este valor
               $_SESSION['user_id'] = $user_id; 
               $username = preg_replace("/[^a-zA-Z0-9_\-]+/", "", $username); // Proteção XSS conforme poderíamos imprimir este valor
               $_SESSION['username'] = $username;
               $_SESSION['login_string'] = hash('sha512', $password.$ip_address.$user_browser);
               // Login com sucesso.
               return true;    
         } else {
            // Senha não está correta
            // Nós armazenamos esta tentativa na base de dados
            $now = time();
            $mysqli->query("INSERT INTO login_attempts (user_id, time) VALUES ('$user_id', '$now')");
            return false;
         }
      }
      } else {
         // Nenhum usuário existe. 
         return false;
      }
   }
}

3 Brute Force function.

Force or brute force attacks happen when hackers attempt thousands of different passwords on an account, either randomly through random passwords or through a dictionary of words. In our script, if a user fails to log in for more than 5 times, their account will be blocked.

Create the login_check function:

function checkbrute($user_id, $mysqli) {
   // Retorna a data atual
   $now = time();
   // Todas as tentativas de login são contadas pelas 2 últimas horas. 
   $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); 
      // Executa a query preparada.
      $stmt->execute();
      $stmt->store_result();
      // Se houver mais de 5 tentativas falhas de login
      if($stmt->num_rows > 5) {
         return true;
      } else {
         return false;
      }
   }
}

Force or brute force attacks are difficult to prevent. Some forms of prevention we can use are the use of CAPTCHA tests, blocking user accounts and adding a delay or time interval for failed login attempts, so the user cannot try to log in again for the next 30 seconds.

When faced with this problem, most developers simply block the IP address after a certain amount of failed logins. With various tools to automate the process, these tools can go through a series of proxys and even modify your IP on each request. Blocking all these IP addresses can mean that you are also blocking Ips from legitimate users.

4 Check the login status.

This is done by checking the session variables "user_id" and "login_string". The session variable "login_string" has the IP address information and browser hashed together with the password. We use the IP address and browser information as it is very unlikely that the user will change the IP address or browser during your session. By doing this, you prevent a hijack attack in the session (session hijacking, literally).

Create the login_check function:

function login_check($mysqli) {
   // Verifica se todas as variáveis das sessões foram definidas
   if(isset($_SESSION['user_id'], $_SESSION['username'], $_SESSION['login_string'])) {
     $user_id = $_SESSION['user_id'];
     $login_string = $_SESSION['login_string'];
     $username = $_SESSION['username'];
     $ip_address = $_SERVER['REMOTE_ADDR']; // Pega o endereço IP do usuário 
     $user_browser = $_SERVER['HTTP_USER_AGENT']; // Pega a string do usuário.

     if ($stmt = $mysqli->prepare("SELECT password FROM members WHERE id = ? LIMIT 1")) { 
        $stmt->bind_param('i', $user_id); // Atribui "$user_id" ao parâmetro
        $stmt->execute(); // Executa a tarefa atribuía
        $stmt->store_result();

        if($stmt->num_rows == 1) { // Caso o usuário exista
           $stmt->bind_result($password); // pega variáveis a partir do resultado
           $stmt->fetch();
           $login_check = hash('sha512', $password.$ip_address.$user_browser);
           if($login_check == $login_string) {
              // Logado!!!
              return true;
           } else {
              // Não foi logado
              return false;
           }
        } else {
            // Não foi logado
            return false;
        }
     } else {
        // Não foi logado
        return false;
     }
   } else {
     // Não foi logado
     return false;
   }
}

Creating Processing Pages

1 *Create the login processing page (process_login.php).*

Following our previous example, this should be called process_login.php. We will use the PHP function set mysqli_* because it is the most current extension of mysql.

Login processing page (process_login.php)

include 'db_connect.php';
include 'functions.php';
sec_session_start(); // Nossa segurança personalizada para iniciar uma sessão php. 

if(isset($_POST['email'], $_POST['p'])) { 
   $email = $_POST['email'];
   $password = $_POST['p']; // A senha em hash.
   if(login($email, $password, $mysqli) == true) {
      // Login com sucesso
      echo 'Sucesso: Você efetuou login.';
   } else {
      // Falha de login
      header('Lozalização: ./login.php?error=1');
   }
} else { 
   // As variáveis POST corretas não foram enviadas para esta página.
   echo 'Requisição Inválida';
}

2 Create a logout script.

Your logout script should log in, destroy it, and then redirect the user to some other page.

Logout script (logout.php):

include 'functions.php';
sec_session_start();
// Zera todos os valores da sessão
$_SESSION = array();
// Pega os parâmetros da sessão 
$params = session_get_cookie_params();
// Deleta o cookie atual.
setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"]);
// Destrói a sessão
session_destroy();
header('Lozalização: ./');

Note: It might be a good idea to add CSFR protection here in case someone sends a link to this page in any way. The user will be dropped.

3 Registration page.

To create the password hash, you will need to use the following code:

Hash script:

// A senha em hash do formulário
$password = $_POST['p']; 
// Cria um salt randômico
$random_salt = hash('sha512', uniqid(mt_rand(1, mt_getrandmax()), true));
// Cria uma senha pós hash (Cuidado para não re-escrever)
$password = hash('sha512', $password.$random_salt);

// Adicione sua inserção ao script de base de dados aqui 
// Certifique-se de utilizar declarações preparadas
if ($insert_stmt = $mysqli->prepare("INSERT INTO members (username, email, password, salt) VALUES (?, ?, ?, ?)")) {    
   $insert_stmt->bind_param('ssss', $username, $email, $password, $random_salt); 
   // Execute a query preparada.
   $insert_stmt->execute();
}

Make sure that the value of $_POST['p'] is already hashed from javascript. If you are not using this method because you want to validate the password on the server side, make sure to use hash.

Create Javascript Files

1 Create a sha512.js file.

This file is a javascript implementation of the sha512 hashing algorithm. We will use the hashing function so that our passwords are not sent in text for years. The file can be downloaded from http://pajhome.org.uk/crypt/md5/sha512.html

2 Create the Forms.js file.

This file will handle the password hash for any form.

Javascript Form File (Forms.js):

function formhash(form, password) {
   // Cria um novo elemento de entrada, como um campo de entrada de senha sem hash.
   var p = document.createElement("input");
   // Adiciona o novo elemento ao nosso formulário.
   form.appendChild(p);
   p.name = "p";
   p.type = "hidden"
   p.value = hex_sha512(password.value);
   // Certifica que senhas em texto plano não sejam enviadas.
   password.value = "";
   // Finalmente, submete o formulário.
   form.submit();
}

Create HTML Pages

1 Create the login form (login.php).

This is an HTML form with two text fields, called "email" and "password". Javascript will then generate the password hash and send "email" and "p" (the password hash) to the server.

When logging in, it is best to use something non-public. For this guide, we are using email as login; the username can be used later to identify the user. If the email is hidden, a new variable will be added for attempts to hack accounts.

HTML Login Form:

<script type="text/javascript" src="sha512.js"></script>
<script type="text/javascript" src="forms.js"></script>
<?php
if(isset($_GET['error'])) { 
   echo 'Erro ao Logar!';
}
?>
<form action="process_login.php" method="post" name="login_form">
   Email: <input type="text" name="email" /><br />
   Password: <input type="password" name="password" id="password"/><br />
   <input type="button" value="Login" onclick="formhash(this.form, this.form.password);" />
</form>

Note: Even if we encrypted the password so that it is not sent in plain text, it is recommended that you use the https (TLS/SSL) protocol when sending passwords.

Protecting Páginas

1 Script for Page Protection.

One of the most common problems with authentication systems is that the developer forgets to check if the user is logged in. It is very important that you use the code below to check if the user is logged in. Page protection:

// Inclua a conexão com a base de dados e as funções aqui.
sec_session_start();
if(login_check($mysqli) == true) {

   // Adicione o conteúdo de sua página protegida aqui.

} else {
   echo 'Você não está autorizado a acessar esta página. Por favor, efetue login. <br/>';
}

END ;)

Source: http://www.wikihow.com/Create-a-Secure-Login-Script-in-PHP-and-MySQL

  • This script is very safe, but how do I add new fields ? I created a field in the database called "Native", but I can only record something in it if I type in Register.inc.php if ($insert_stmt = $mysqli->prepare("INSERT INTO Members (username, email, password, salt) VALUES (?, ?, ?, ?)")), what do I do to get this data in the file it records ? and to record it in the database ?

10

Question: What is the best way to make a password login system with PHP?

Answer: There is no "the best way". There are several quality solutions, each one being the best within a specific context.

  • 4

    It may not be "the best answer," but it’s the right answer.

  • I have been using the Laravel 4 framework. I have made public the authentication/authorization solution I am using. It is based on the Wordpress solution (which does not use Session): https://github.com/jbruni/larauth

9

When it comes to user authentication, "safe" and "simple" will hardly make sense. The way to do less work in this case, without sacrificing system security, is to use a framework that already implements this for you. The Symfony2 provides an authentication library that can be used standalone, see Docs.

My answer presumes that its interest is to create a system that will eventually go into production; if your interest is educational you can use a simple hashing algorithm and store the name and password combinations in a local database and then dig deeper topics like hashing, salting, etc.

7

In PHP use Argon2id, this is available in PHP 7.2:

$hash_str = sodium_crypto_pwhash_str($password, 
       SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
       SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);

Then use the sodium_crypto_pwhash_str_verify($hash_str, $password).

You can also use the Argon2i:

$hash_str = password_hash($password , PASSWORD_ARGON2I, [
          'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST, 
          'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST, 
          'threads' => PASSWORD_ARGON2_DEFAULT_THREADS
]);

Then use the password_​verify($password, $hash_str).

If you are in an obsolete PHP version, then security does not seem to be your priority. If you are still using a "supported" version but do not have Argon2, use Bcrypt: password_hash($password , PASSWORD_BCRYPT, ['cost' => 13]);. If neither Bcrypt is available, then use PBKDF2.

Several answers have serious problems!

  1. Utilise rand(), mt_rand() or uniqid() and even time()...

    All of these are good for generating unique data to some extent. Spotify uses mt_rand() to choose a random order of the songs, for that purpose he is good, he is fast. A game can use it to create the smoke effects, ok. Now, for passwords it doesn’t need to be just unique. It needs to be unique, impressively (for the attacker) and still have an equal result distribution. Therefore, an attacker cannot predict the next value and recover previously generated values. Depending on the generator, not even attacking the entropy pool, consumed for re-seed, will be able to know the generated bytes, why not know the current state.

    The mt_rand() will repeat all values in the same order after X generated numbers. Also, with a sample of ~700 numbers generated you can predict all the next.

    The maximum pearl is to use time(), look at your watch and you know the value, in fact it is unique (it is unixtime), but it is clearly predictable. In fact, it is not unique, if they attack the NTP server that you probably use to synchronize the date-time you will go back in time.

    Utilize random_bytes or simply read the /dev/urandom, if applicable. These two are CSPRNG and should be used for these purposes.

  2. Utilise SHA-2, SHA-1, MD5, SHA-3....

    This was done for general purpose, not for passwords. Passwords are inputs chosen by humans and we are incompetent to choose good passwords. We can not remember extremely complex passwords, many appeal to memories or personal data. This makes it vulnerable to pre-computed attacks or dictionary attacks.

    The best choices you have are Bcrypt and Argon2i, both of which are available in PHP 7.2. While Bcrypt and PBKDF2 are available since PHP 5.5. All of these are key derivation (KDF) or also called Password Hashing, this differs from general purpose hashes such as SHA-2.

    In the Argon2i the difficulty, which is customizable. This involves the greater use of memory, the greater use of parallel processing, and the number of rounds of iterations required for a single password. The greater the difficulty, the longer will be the time to do the hashing of a single string. Bcrypt has similar features, but cannot be increased individually. PBKDF2 has only the amount of iterations (which is basically N * HMAC(), a very roughly way), this is the worst of the best methods you can use.

  3. Use the salt and believe that he is salvation.

    The salt is not miraculous. In him DOES NOT DELAY an exhaustive search attack. If you have HASH(x) and HASH(x + y) the time difference to compute the two is extremely negligible, if the y for a few bytes it will be the same. Also if you do HASH(salt + senha) the attacker can ignore the salt in full, if it is the same size (or larger) as the hash block.

    The salt is made only so that two passwords are different from each other and only. If you have a function HASH(string, salt), and then do BCRYPT(x, y) and BCRYPT(x, z) the result for the same x will be different, because the Salts (y and z) are different. This prevents an attacker from breaking multiple passwords at the same time, only.

    The cost against Adobe Force is much more complex, ranging from memory usage, parallelism, pre-computation capacity and even the number of targets. This is what is done in the use of Argon2, Bcrypt and even reasonably in PBKDF2. These functions are not good because they use salt, salt is made for a different purpose to increase than to increase the difficulty.

  4. Utilise == and ===.

    This is vulnerable to timming-Attack. This applies to all languages, practically. Most languages use memcmp() or similar characteristics, which makes it fast but not safe.

    But not only does PHP have a dynamic typing feature and this brings several unexpected effects to some. In general, never use ==, or you can see something like sha1('aaroZmOk') == sha1('aaK1STfY') return true. That’s not a SHA-1 collision, that’s just because they both start with 0e and the == does the job of converting and then becoming valid, which does not occur with ===.

    PHP has the hash_equals(), despite the name it can be used for any purpose, ie hash_equals('a', 'a') works. Despite the prefix hash_ the strings involved don’t have to be a cryptographic hash.

  • I would suggest you introduce password_hash and password_verify at the beginning of your answer, answering the question directly, and leaving the criticism part at the end, because if your answer is complete and canonical, we can do a campaign to highlight it or launch a bonus. Who knows if you leave it complete give up to ask @Mathdesigner47 to reconsider acceptance - it would be nice to also cross post with other questions with good answers on the subject of the site.

  • @Bacco added the basics of password_hash, then I edit the rest. : S

  • I had already uploaded, I hope @Mathdesigner47 will stick to your post, and even if you don’t mark as accepted, at least reconsider "disavowing" the current one, so that naturally the community can vote correctly. I think the ideal would really be to gradually compose a canonical well completed, so that we can at least compensate in the votes.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.