Hello, this is a "doodle" of a tutorial login system that I recently created, it is not the most complex, but it will help you understand the basics about security currently.
Login & registration form
<?php
// index.php
require_once("database.php");
// [Nao usar estes modelos em aplicacoes reais]
// Estes são apenas para demonstração e teste, e não são nada seguros
if(isset($_POST["submit"]) && isset($_POST["tipo"]) && $_POST["tipo"] === "novo"){
$usuario = $_POST["usuario"];
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
$password = $_POST["password"];
// Limpar caracteres invalidos
// Incompleto [Nao usar estes modelos em aplicacoes reais]
// Validar Letras para o usuarios e remover espaços brancos com o trim()
$usuario = preg_replace("/[^A-Za-z0-9]/","", $usuario) ? trim($usuario) : NULL;
// Validar Conjunto alfa-numérico para o password e remover espaços brancos com o trim()
$password = preg_match("/[A-Za-z0-9]/", $password) ? trim($password) : NULL;
if($usuario !== NULL && $password !== NULL){
$sim = registar($usuario, $email, $password);
if($sim){
header("Location: index.php");
exit;
} else {
//Erro para o caso de o usuario ja existir, ou nao ser armazenado
echo "<span style=\"color:red;\">erro: cadastro falhou, tente novamente.</span>";
exit;
}
} else {
//Erro para o caso de a senha, usuario, email estar(em) vazio(s)
echo "<span style=\"color:red;\">erro: usuario/senha vazio(s)</span>";
exit;
}
}
if(isset($_POST["submit"]) && isset($_POST["tipo"]) && $_POST["tipo"] === "entrar"){
$usuario = $_POST["usuario"];
// $email = $_POST["email"];
$password = $_POST["password"];
// Limpar caracteres invalidos
$usuario = preg_replace("/[^A-Za-z0-9]/","", $usuario) ? trim($usuario) : NULL;
$password = preg_match("/[A-Za-z0-9]/", $password) ? trim($password) : NULL;
if($usuario !== NULL && $password !== NULL){
$sim = login($usuario, $password);
if($sim){
header("Location: privado.php");
exit;
} else {
//Erro para o caso de o usuario nao ser encontrado ou para o caso de os dados nao corresponderem
echo "<span style=\"color:red;\">erro: usuario/senha nao encontrados</span>";
exit;
}
} else {
//Erro para o caso de a senha e usuario estar(em) vazio(s)
echo "<span style=\"color:red;\">erro: usuario/senha vazio(s)</span>";
exit;
}
}
?>
<!-- HTML !-->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Login/Cadastro [Seguro]</title>
</head>
<body>
<?php
if(isset($_GET["opcao"]) && $_GET["opcao"] === "entrar"){
?>
<!-- LOGIN !-->
<h1>Login</h1>
<form method="POST" action="index.php">
<input type="hidden" name="tipo" value="entrar"/>
Usuario:<br/>
<input type="text" name="usuario" value="" size="40"/><br/>
Password:<br/>
<input type="password" name="password" value="" size="40"/><br/>
<input type="submit" name="submit" value="Entrar"/>
</form>
<a href="index.php?opcao=novo">Cadastrar</a><br/><br/>
<?php
} elseif(isset($_GET["opcao"]) && $_GET["opcao"] === "novo"){
?>
<!-- CADASTRO !-->
<h1>Cadastrar</h1>
<form method="POST" action="index.php">
<input type="hidden" name="tipo" value="novo"/>
Usuario:<br/>
<input type="text" name="usuario" value="" size="40"/><br/>
Email:<br/>
<input type="email" name="email" value="" size="40"/><br/>
Password:<br/>
<input type="password" name="password" value="" size="40"/><br/>
<input type="submit" name="submit" value="Entrar"/>
</form>
<a href="index.php?opcao=entrar">Login</a><br/><br/>
<?php
} else {
?>
<!-- LOGIN !-->
<h1>Login</h1>
<form method="POST" action="index.php">
<input type="hidden" name="tipo" value="entrar"/>
Usuario:<br/>
<input type="text" name="usuario" value="" size="40"/><br/>
Password:<br/>
<input type="password" name="password" value="" size="40"/><br/>
<input type="submit" name="submit" value="Entrar"/>
</form>
<a href="index.php?opcao=novo">Cadastrar</a><br/><br/>
<?php
}
?>
</body>
</html>
The Login and Registration Form are in a single file along with the validation for both.
In a login and registration script, it is always crucial sanear, validar
information from the forms to verify that they are of the right size, or that they are not problematic.
Pay attention to the notes in the script, and notice that I haven’t even used a decent validation.
Database
<?php
// database.php
session_start();
// Em fase de correcção não vamos querer essa função no ativo
// Se esta linha for descomentada, os erros estarao visiveis apenas no log.log
// ini_set("error_reporting", "E_ALL");
require_once("blowfish.php");
// Host, normalmente é o local
DEFINE("HOST", "localhost");
// O port, na maior parte das vezes é dispensavel
// Ainda assim o POST inicia conexoes seguras/nao seguras dependendo do PORT usado
// DEFINE("PORT", "80");
// Usuario do banco de dados
DEFINE("USR", "root");
// Senha do usuario do banco de dados
DEFINE("PWD", "");
// Banco de dados
DEFINE("BD", "_banco_de_dados_em_uso_");
// Ficheiro de log, caso não exista, crie um manualmente;
// Ou crie uma função que o faça de forma segura e autonoma
$error_log = "log.log";
$db = new mysqli(HOST, USR, PWD, BD);
if(mysqli_connect_errno()){
error_logi("Conexao falhou", mysqli_connect_error());
}
function error_logi($error,$msg=""){
global $error_log;
$log_msg = $error . " : " . $msg . PHP_EOL;
return file_put_contents($error_log, $log_msg, FILE_APPEND | LOCK_EX);
exit;
}
// Função para encontrar usuario por nome;
function encontrar_usuario($usuario){
global $db;
($stmt = $db->prepare("SELECT username,senha FROM usuarios WHERE username=?")) || error_logi("STMT Encontrar Usuario", $db->error);
$stmt->bind_param('s', $usuario) || error_logi("STMT Bind Param", $db->error);
$stmt->execute() || error_logi("STMT Execute", $db->error);
$stmt->bind_result($username,$senha) || error_logi("STMT Bind Result", $db->error);
$stmt->fetch();
$result = ["username"=>$username,"senha"=>$senha];
return $result;
}
// Função para efectuar o registo;
function registar($usuario, $email, $password){
global $db;
$password = hash_password($password);
($stmt = $db->prepare("INSERT INTO usuarios (username, email, senha) VALUES (?, ?, ?)"))
|| error_logi("SQL Prepared Statment",$db->error);
($stmt->bind_param('sss', $usuario, $email, $password)) || error_logi("SQL BindParam",$db->error);
$exec = $stmt->execute() ? true : error_logi("SQL Execute",$db->error);
return $exec;
$stmt->close();
$db->close();
}
// Tentar fazer o login
function login($usuario, $password){
$usuario = encontrar_usuario($usuario);
if($usuario){
// usuario encontrado
// Verificar a hash para a password
if(verifica_hash($password, $usuario["senha"])){
$_SESSION["usuario"] = $usuario["username"];
return true;
} else {
// hash não encontrada
return false;
}
} else {
// usuario não encontrado
return false;
}
}
function check_login($usuario){
$existe = encontrar_usuario($usuario);
if($existe){
return $existe["username"] === $usuario ? true : false;
} else {
return false;
}
}
?>
In the database the password field must be of the type VARCHAR(60)
in order to be able to store the hash
.
Private page (restricted to people without a session)
<?php
// privado.php
// Esta é a página protegida
require_once("database.php");
if(isset($_SESSION["usuario"])){
if(check_login($_SESSION["usuario"])){
echo "Logado";
// Isto é um sistema para teste, daí usar esta função aqui
// Significa que a página só pode ser visualizada apenas 1 vez por login
session_destroy();
} else {
header("Location:index.php");
exit;
}
} else {
header("Location:index.php");
exit;
}
?>
HASH
<?php
// blowfish.php
// Script util apenas para versões do PHP < 5.5.0;
// Função que gera a hash
function hash_password($password){
$formato = "$2y$10$";
$salt = salt(22);
$formato_salt = $formato.$salt;
$password_hash = crypt($password, $formato_salt);
return $password_hash;
// Se algo correr mal a função vai retornar falso;
}
// Função que gera o salt
function salt($tamanho){
//$random = md5(uniqid(mt_rand(), true));
// ambas funções geram valores aleatorios
$random = md5(uniqid(mcrypt_create_iv(22, MCRYPT_DEV_URANDOM), true));
$base = base64_encode($random);
$base64 = str_replace('+', '.', $base);
$salt = substr($base64, 0, $tamanho);
return $salt;
}
// Função para comparar as duas hash
function verifica_hash($password, $hash_existente){
$hash = crypt($password, $hash_existente);
if($hash === $hash_existente){
return true;
} else {
return false;
}
}
?>
In this script above, the functions are only for the PHP < 5.5.0
, for higher versions recommend replacing the functions in the database.php file hash_password, verifica_hash
by the new functions officially introduced by PHP >= 5.5.0
:
password_hash()
password_verify()
And if possible, use these functions instead of using the file blowfish.php
.
there is also a API
for versions of PHP >= 5.3.7
that initializes these new functions of PHP >= 5.5.0
in lower versions, it is sufficient that the script in use makes a require
of this API
.
PASSWORD COMPAT API
Other safety tips:
*Avoid users of type root
to handle customer applications.
*Avoid saving passwords as texto simples
or plain-text
for English.
*Requests of the type GET
must always be idempotentes
, that is, unable to make modifications on the server side (for modifications use POST
).
In addition to these cited, there are several points where one should pay attention. However I leave only this summary, and good luck.
Wikihow - How to create a Secure login script in PHP and Mysql
Github - PHP Login Advanced
Of course, I already edit the question
– Lucas Caresia
Related: "How to hash passwords securely?"
– mgibsonbr