Hello, this is a "scribble" of a tutorial login system I created recently, it is not the most complex, but it will help you understand the basics about security today.
Login form & registration
<?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 is in a single file along with the validation for both.
In a login script and registration, it is always crucial to sanear, validar
the information coming from the forms in order to verify if they have the right size, or if it does not contain any problematic character.
Pay attention to the notes in the script, and notice that I did not even use 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 type VARCHAR(60)
to be able to store hash
.
Private Page (restricted to non-members)
<?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, functions are only for PHP < 5.5.0
, for higher versions I recommend replacing in the database.php file the hash_password, verifica_hash
functions with new functions officially entered by PHP >= 5.5.0
:
password_hash ()
password_verify ()
And if possible, use these functions instead of using the blowfish.php
file.
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, just that the script in use makes a require
of this API
.
PASSWORD COMPAT API
Other security tips:
* Avoid users of type root
to handle applications as a client.
* Avoid saving passwords as texto simples
or plain-text
to English.
* Requests of type GET
must always be idempotentes
, that is, unable to perform modifications on the server side (for modifications use POST
).
In addition to these, there are several points to note. 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