Authentication system (no browser) with mail verification












1












$begingroup$


I made an authentication system for my software (no browser is used) that works like this:




  • User receives a Key on it's mail after buying the software

  • User registers account using that key and inputs username, mail, password.

  • registration.php checks if the format is correct then sends an email with a confirmation code using a template and inserts the values into DB (verify table).

  • The HTML template contains username (from user input), 2 images from the same directory as the registration.php and the template2.html and a button with a get like this: "www.mysite.com/activate.php?id=$serial&conf=$confirmation_code".

  • activate.php checks if everything is valid and then inserts the verify table's row into accounts table. (after it is inserted into accounts table, it becomes a valid account).


Everything works as expected (I'm still making the login.php).



Why I'm posting this here:



As a beginner (this is the first php, mysql and html thing I do) I know there is a lot of things that can be improved. My goal is to make it as secure as possible, I followed OWASP as much as I could and at the same time I read a lot on Stack Overflow and Security.Stackexchange.



Here is my code:



registration.php



<?php
$CurrentVersionHash="asd123";
$CurrentVersion="alpha1";
$CurrentHash="asd123";
$serial_length="10";
$memory_cost="31250";
$time_cost="10";
$threads="1";
require 'mail.php';

if ( ! empty( $_POST ) ) // Check if Post is not empty
{
$hash = $_POST['exe'];
$pw = $_POST['pass'];
$user = $_POST['name'];
$version = $_POST['ver'];
$email = $_POST['mail'];
$confirm = $_POST['pass2'];
$serial = $_POST['key'];
if ( empty($hash) or empty($version))
return;
if ( $hash<>$CurrentHash) // Check if file Hash is valid
{
if ( $version<>$CurrentVersionHash ) // Check if it's because it's outdated
{
echo "Old exe";
}
else
{
echo "Exe not valid";
}
return;
}
// Checking if there is any problem in the format
if ( (empty( $user )) or ( strlen( $user ) < 6 ) or ( strlen( $user ) > 254 ) or ( ! ctype_alnum ( $user )) )
{
echo "Error";
return;
}
if ( (empty( $pw)) or (strlen($pw) < 10) or (strlen($pw) > 254) or (! preg_match("#[0-9]+#", $pw)) or ( !preg_match("#[a-z]+#", $pw )) or ( !preg_match("#[A-Z]+#", $pw )) )
{
echo "Error";
return;
}
if ( $pw != $confirm or empty( $confirm ) )
{
echo "Error";
return;
}
if (( empty( $email )) or ( ! filter_var($email, FILTER_VALIDATE_EMAIL)) or (strlen($email) > 254) )
{
echo "Error";
return;
}
if ( ( empty( $serial ) ) or ( strlen( $serial ) <> $serial_length ) )
{
echo "Error";
return;
}
// Check if Serial exists and is not already taken
$con = new mysqli($host, $username, $password, $database);
$stmt = $con->prepare("SELECT * FROM acc WHERE serial=?");
$stmt->bind_param('s', $serial);
$stmt->execute();
$result = $stmt->get_result();
$num_of_rows = $result->num_rows;
$result = $result->fetch_array();
// If Serial doesn't exist or is already taken
if ( ($num_of_rows==0) or ( ! empty( $result['username'] )) )
{
echo "Invalid Serial";
$error=1;
}
$stmt = $con->prepare("SELECT * FROM verify WHERE serial=?");
$stmt->bind_param('s', $serial);
$stmt->execute();
$result = $stmt->get_result();
$num_of_rows = $result->num_rows;
$result = $result->fetch_array();
// If Serial is on verify DB (means that someone already registered using it)
if ( ($num_of_rows<>0) and ($error<>1) )
{
echo "Invalid Serial";
$error=1;
}
if ($error<>1)
{
// Check if Username or E-mail is not already taken
$stmt = $con->prepare("SELECT * FROM acc WHERE username= ? OR email= ? LIMIT 1");
$stmt->bind_param('ss', $user, $email);
$stmt->execute();
$result = $stmt->get_result();
$num_of_rows = $result->num_rows;
$result = $result->fetch_array();
$stmt = $con->prepare("SELECT * FROM verify WHERE username= ? OR email= ? LIMIT 1");
$stmt->bind_param('ss', $user, $email);
$stmt->execute();
$result2 = $stmt->get_result();
$num_of_rows2 = $result2->num_rows;
$result2 = $result2->fetch_array();
if ( ($num_of_rows) or ($num_of_rows2) ) // If user or email already exists on verify and acc DB
{
if ( (strcasecmp($result['username'], $user) == 0) or (strcasecmp($result2['username'], $user) == 0) ) // If user already exists
{
echo "Username in use";
}
else if ( (strcasecmp($result['email'], $email) == 0) or (strcasecmp($result2['email'], $email) == 0) )// If e-mail is already taken
{
echo "Email in use";
}
}
else
{
// Do registration
$password = password_hash( $pw, PASSWORD_ARGON2ID, [
'memory_cost' => $memory_cost,
'time_cost' => $time_cost,
'threads' => $threads,
]);
$rand_id = random_int(-10000, 10000);
$rand_secret = random_str(32);
$stmt = $con->prepare("INSERT INTO verify (username, password, email, serial, rand_id, rand_secret) VALUES (?,?,?,?,?,?)");
$stmt->bind_param('ssssss', $user, $password, $email, $serial, $rand_id, $rand_secret);
$stmt->execute();
$confirmation_code = hash_hmac('sha256', $rand_id, $rand_secret);
$variables = array();
$variables['User'] = $user;
$variables['Serial'] = $serial;
$variables['Code'] = $confirmation_code;
$template = file_get_contents("template2.html");
foreach($variables as $key => $value)
{
$template = str_replace('{{ '.$key.' }}', $value, $template);
}
$mail->addAddress($email, $user);
$mail->msgHTML($template, __DIR__);
$mail->AltBody = "You can activate your account here: www.mysite.com/activate.php?id=$serial&conf=$confirmation_code";
if (!$mail->send())
{
echo 'error, mail not delivered';
}
else
{
echo "Registration OK! mail sent";
}
}
}
$stmt->close();
$con->close();
return;
}

function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
$pieces = ;
$max = mb_strlen($keyspace, '8bit') - 1;
for ($i = 0; $i < $length; ++$i) {
$pieces = $keyspace[random_int(0, $max)];
}
return implode('', $pieces);
}
?>


activate.php



<?php
if ( ! empty( $_GET ) )
{
if ( isset($_GET['id']) and isset($_GET['conf']) )
{
$key=$_GET['id'];
$code=$_GET['conf'];
$con = new mysqli($host, $username, $password, $database);
$stmt = $con->prepare("SELECT * FROM verify WHERE serial=?");
$stmt->bind_param('s', $key);
$stmt->execute();
$result = $stmt->get_result();
$num_of_rows = $result->num_rows;
$result = $result->fetch_array();
// If Serial doesn't exist or is already taken
if ( ($num_of_rows==0) or ( empty( $result['username'] )) or ( empty( $result['rand_id'] )) or ( empty( $result['rand_secret'] )) )
{
$error=1;
echo "Invalid code";
}
if ( $error<>1 )
{
$rand_id = $result['rand_id'];
$rand_secret = $result['rand_secret'];
$confirmation_code = hash_hmac('sha256', $rand_id, $rand_secret);
if ($confirmation_code == $code)
{
$user=$result['username'];
$password=$result['password'];
$email=$result['email'];
$stmt = $con->prepare("UPDATE accounts SET username = ?, password = ?, email = ? WHERE Serial = ?");
$stmt->bind_param('ssss', $user, $password, $email, $key);
$stmt->execute();
$stmt = $con->prepare("DELETE from verify WHERE serial = ?");
$stmt->bind_param('s', $key);
$stmt->execute();
echo "Account activated";
}
else
{
$error=1;
echo "Invalid code";
}
}
$stmt->close();
$con->close();
return;
}
}

function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
$pieces = ;
$max = mb_strlen($keyspace, '8bit') - 1;
for ($i = 0; $i < $length; ++$i) {
$pieces = $keyspace[random_int(0, $max)];
}
return implode('', $pieces);
}
?>


mail.php



<?php
use PHPMailerPHPMailerPHPMailer;
require 'vendor/autoload.php';
$mail = new PHPMailer;
$mail->isSMTP();
$mail->Host = 'smtphost';
$mail->Port = port;
$mail->SMTPAuth = true;
$mail->Username = 'mymail';
$mail->Password = 'mypassword';
$mail->setFrom('mymail', 'myname');
$mail->addReplyTo('mysupportmail', 'myname');
$mail->AddEmbeddedImage('img/logo.png', 'mylogo');
$mail->AddEmbeddedImage('img/gif.gif', 'mygif');
$mail->Subject = "Activation for account";
?>


Thanks in advance.










share|improve this question









New contributor




Roberto Carlos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.







$endgroup$

















    1












    $begingroup$


    I made an authentication system for my software (no browser is used) that works like this:




    • User receives a Key on it's mail after buying the software

    • User registers account using that key and inputs username, mail, password.

    • registration.php checks if the format is correct then sends an email with a confirmation code using a template and inserts the values into DB (verify table).

    • The HTML template contains username (from user input), 2 images from the same directory as the registration.php and the template2.html and a button with a get like this: "www.mysite.com/activate.php?id=$serial&conf=$confirmation_code".

    • activate.php checks if everything is valid and then inserts the verify table's row into accounts table. (after it is inserted into accounts table, it becomes a valid account).


    Everything works as expected (I'm still making the login.php).



    Why I'm posting this here:



    As a beginner (this is the first php, mysql and html thing I do) I know there is a lot of things that can be improved. My goal is to make it as secure as possible, I followed OWASP as much as I could and at the same time I read a lot on Stack Overflow and Security.Stackexchange.



    Here is my code:



    registration.php



    <?php
    $CurrentVersionHash="asd123";
    $CurrentVersion="alpha1";
    $CurrentHash="asd123";
    $serial_length="10";
    $memory_cost="31250";
    $time_cost="10";
    $threads="1";
    require 'mail.php';

    if ( ! empty( $_POST ) ) // Check if Post is not empty
    {
    $hash = $_POST['exe'];
    $pw = $_POST['pass'];
    $user = $_POST['name'];
    $version = $_POST['ver'];
    $email = $_POST['mail'];
    $confirm = $_POST['pass2'];
    $serial = $_POST['key'];
    if ( empty($hash) or empty($version))
    return;
    if ( $hash<>$CurrentHash) // Check if file Hash is valid
    {
    if ( $version<>$CurrentVersionHash ) // Check if it's because it's outdated
    {
    echo "Old exe";
    }
    else
    {
    echo "Exe not valid";
    }
    return;
    }
    // Checking if there is any problem in the format
    if ( (empty( $user )) or ( strlen( $user ) < 6 ) or ( strlen( $user ) > 254 ) or ( ! ctype_alnum ( $user )) )
    {
    echo "Error";
    return;
    }
    if ( (empty( $pw)) or (strlen($pw) < 10) or (strlen($pw) > 254) or (! preg_match("#[0-9]+#", $pw)) or ( !preg_match("#[a-z]+#", $pw )) or ( !preg_match("#[A-Z]+#", $pw )) )
    {
    echo "Error";
    return;
    }
    if ( $pw != $confirm or empty( $confirm ) )
    {
    echo "Error";
    return;
    }
    if (( empty( $email )) or ( ! filter_var($email, FILTER_VALIDATE_EMAIL)) or (strlen($email) > 254) )
    {
    echo "Error";
    return;
    }
    if ( ( empty( $serial ) ) or ( strlen( $serial ) <> $serial_length ) )
    {
    echo "Error";
    return;
    }
    // Check if Serial exists and is not already taken
    $con = new mysqli($host, $username, $password, $database);
    $stmt = $con->prepare("SELECT * FROM acc WHERE serial=?");
    $stmt->bind_param('s', $serial);
    $stmt->execute();
    $result = $stmt->get_result();
    $num_of_rows = $result->num_rows;
    $result = $result->fetch_array();
    // If Serial doesn't exist or is already taken
    if ( ($num_of_rows==0) or ( ! empty( $result['username'] )) )
    {
    echo "Invalid Serial";
    $error=1;
    }
    $stmt = $con->prepare("SELECT * FROM verify WHERE serial=?");
    $stmt->bind_param('s', $serial);
    $stmt->execute();
    $result = $stmt->get_result();
    $num_of_rows = $result->num_rows;
    $result = $result->fetch_array();
    // If Serial is on verify DB (means that someone already registered using it)
    if ( ($num_of_rows<>0) and ($error<>1) )
    {
    echo "Invalid Serial";
    $error=1;
    }
    if ($error<>1)
    {
    // Check if Username or E-mail is not already taken
    $stmt = $con->prepare("SELECT * FROM acc WHERE username= ? OR email= ? LIMIT 1");
    $stmt->bind_param('ss', $user, $email);
    $stmt->execute();
    $result = $stmt->get_result();
    $num_of_rows = $result->num_rows;
    $result = $result->fetch_array();
    $stmt = $con->prepare("SELECT * FROM verify WHERE username= ? OR email= ? LIMIT 1");
    $stmt->bind_param('ss', $user, $email);
    $stmt->execute();
    $result2 = $stmt->get_result();
    $num_of_rows2 = $result2->num_rows;
    $result2 = $result2->fetch_array();
    if ( ($num_of_rows) or ($num_of_rows2) ) // If user or email already exists on verify and acc DB
    {
    if ( (strcasecmp($result['username'], $user) == 0) or (strcasecmp($result2['username'], $user) == 0) ) // If user already exists
    {
    echo "Username in use";
    }
    else if ( (strcasecmp($result['email'], $email) == 0) or (strcasecmp($result2['email'], $email) == 0) )// If e-mail is already taken
    {
    echo "Email in use";
    }
    }
    else
    {
    // Do registration
    $password = password_hash( $pw, PASSWORD_ARGON2ID, [
    'memory_cost' => $memory_cost,
    'time_cost' => $time_cost,
    'threads' => $threads,
    ]);
    $rand_id = random_int(-10000, 10000);
    $rand_secret = random_str(32);
    $stmt = $con->prepare("INSERT INTO verify (username, password, email, serial, rand_id, rand_secret) VALUES (?,?,?,?,?,?)");
    $stmt->bind_param('ssssss', $user, $password, $email, $serial, $rand_id, $rand_secret);
    $stmt->execute();
    $confirmation_code = hash_hmac('sha256', $rand_id, $rand_secret);
    $variables = array();
    $variables['User'] = $user;
    $variables['Serial'] = $serial;
    $variables['Code'] = $confirmation_code;
    $template = file_get_contents("template2.html");
    foreach($variables as $key => $value)
    {
    $template = str_replace('{{ '.$key.' }}', $value, $template);
    }
    $mail->addAddress($email, $user);
    $mail->msgHTML($template, __DIR__);
    $mail->AltBody = "You can activate your account here: www.mysite.com/activate.php?id=$serial&conf=$confirmation_code";
    if (!$mail->send())
    {
    echo 'error, mail not delivered';
    }
    else
    {
    echo "Registration OK! mail sent";
    }
    }
    }
    $stmt->close();
    $con->close();
    return;
    }

    function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
    {
    $pieces = ;
    $max = mb_strlen($keyspace, '8bit') - 1;
    for ($i = 0; $i < $length; ++$i) {
    $pieces = $keyspace[random_int(0, $max)];
    }
    return implode('', $pieces);
    }
    ?>


    activate.php



    <?php
    if ( ! empty( $_GET ) )
    {
    if ( isset($_GET['id']) and isset($_GET['conf']) )
    {
    $key=$_GET['id'];
    $code=$_GET['conf'];
    $con = new mysqli($host, $username, $password, $database);
    $stmt = $con->prepare("SELECT * FROM verify WHERE serial=?");
    $stmt->bind_param('s', $key);
    $stmt->execute();
    $result = $stmt->get_result();
    $num_of_rows = $result->num_rows;
    $result = $result->fetch_array();
    // If Serial doesn't exist or is already taken
    if ( ($num_of_rows==0) or ( empty( $result['username'] )) or ( empty( $result['rand_id'] )) or ( empty( $result['rand_secret'] )) )
    {
    $error=1;
    echo "Invalid code";
    }
    if ( $error<>1 )
    {
    $rand_id = $result['rand_id'];
    $rand_secret = $result['rand_secret'];
    $confirmation_code = hash_hmac('sha256', $rand_id, $rand_secret);
    if ($confirmation_code == $code)
    {
    $user=$result['username'];
    $password=$result['password'];
    $email=$result['email'];
    $stmt = $con->prepare("UPDATE accounts SET username = ?, password = ?, email = ? WHERE Serial = ?");
    $stmt->bind_param('ssss', $user, $password, $email, $key);
    $stmt->execute();
    $stmt = $con->prepare("DELETE from verify WHERE serial = ?");
    $stmt->bind_param('s', $key);
    $stmt->execute();
    echo "Account activated";
    }
    else
    {
    $error=1;
    echo "Invalid code";
    }
    }
    $stmt->close();
    $con->close();
    return;
    }
    }

    function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
    {
    $pieces = ;
    $max = mb_strlen($keyspace, '8bit') - 1;
    for ($i = 0; $i < $length; ++$i) {
    $pieces = $keyspace[random_int(0, $max)];
    }
    return implode('', $pieces);
    }
    ?>


    mail.php



    <?php
    use PHPMailerPHPMailerPHPMailer;
    require 'vendor/autoload.php';
    $mail = new PHPMailer;
    $mail->isSMTP();
    $mail->Host = 'smtphost';
    $mail->Port = port;
    $mail->SMTPAuth = true;
    $mail->Username = 'mymail';
    $mail->Password = 'mypassword';
    $mail->setFrom('mymail', 'myname');
    $mail->addReplyTo('mysupportmail', 'myname');
    $mail->AddEmbeddedImage('img/logo.png', 'mylogo');
    $mail->AddEmbeddedImage('img/gif.gif', 'mygif');
    $mail->Subject = "Activation for account";
    ?>


    Thanks in advance.










    share|improve this question









    New contributor




    Roberto Carlos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.







    $endgroup$















      1












      1








      1





      $begingroup$


      I made an authentication system for my software (no browser is used) that works like this:




      • User receives a Key on it's mail after buying the software

      • User registers account using that key and inputs username, mail, password.

      • registration.php checks if the format is correct then sends an email with a confirmation code using a template and inserts the values into DB (verify table).

      • The HTML template contains username (from user input), 2 images from the same directory as the registration.php and the template2.html and a button with a get like this: "www.mysite.com/activate.php?id=$serial&conf=$confirmation_code".

      • activate.php checks if everything is valid and then inserts the verify table's row into accounts table. (after it is inserted into accounts table, it becomes a valid account).


      Everything works as expected (I'm still making the login.php).



      Why I'm posting this here:



      As a beginner (this is the first php, mysql and html thing I do) I know there is a lot of things that can be improved. My goal is to make it as secure as possible, I followed OWASP as much as I could and at the same time I read a lot on Stack Overflow and Security.Stackexchange.



      Here is my code:



      registration.php



      <?php
      $CurrentVersionHash="asd123";
      $CurrentVersion="alpha1";
      $CurrentHash="asd123";
      $serial_length="10";
      $memory_cost="31250";
      $time_cost="10";
      $threads="1";
      require 'mail.php';

      if ( ! empty( $_POST ) ) // Check if Post is not empty
      {
      $hash = $_POST['exe'];
      $pw = $_POST['pass'];
      $user = $_POST['name'];
      $version = $_POST['ver'];
      $email = $_POST['mail'];
      $confirm = $_POST['pass2'];
      $serial = $_POST['key'];
      if ( empty($hash) or empty($version))
      return;
      if ( $hash<>$CurrentHash) // Check if file Hash is valid
      {
      if ( $version<>$CurrentVersionHash ) // Check if it's because it's outdated
      {
      echo "Old exe";
      }
      else
      {
      echo "Exe not valid";
      }
      return;
      }
      // Checking if there is any problem in the format
      if ( (empty( $user )) or ( strlen( $user ) < 6 ) or ( strlen( $user ) > 254 ) or ( ! ctype_alnum ( $user )) )
      {
      echo "Error";
      return;
      }
      if ( (empty( $pw)) or (strlen($pw) < 10) or (strlen($pw) > 254) or (! preg_match("#[0-9]+#", $pw)) or ( !preg_match("#[a-z]+#", $pw )) or ( !preg_match("#[A-Z]+#", $pw )) )
      {
      echo "Error";
      return;
      }
      if ( $pw != $confirm or empty( $confirm ) )
      {
      echo "Error";
      return;
      }
      if (( empty( $email )) or ( ! filter_var($email, FILTER_VALIDATE_EMAIL)) or (strlen($email) > 254) )
      {
      echo "Error";
      return;
      }
      if ( ( empty( $serial ) ) or ( strlen( $serial ) <> $serial_length ) )
      {
      echo "Error";
      return;
      }
      // Check if Serial exists and is not already taken
      $con = new mysqli($host, $username, $password, $database);
      $stmt = $con->prepare("SELECT * FROM acc WHERE serial=?");
      $stmt->bind_param('s', $serial);
      $stmt->execute();
      $result = $stmt->get_result();
      $num_of_rows = $result->num_rows;
      $result = $result->fetch_array();
      // If Serial doesn't exist or is already taken
      if ( ($num_of_rows==0) or ( ! empty( $result['username'] )) )
      {
      echo "Invalid Serial";
      $error=1;
      }
      $stmt = $con->prepare("SELECT * FROM verify WHERE serial=?");
      $stmt->bind_param('s', $serial);
      $stmt->execute();
      $result = $stmt->get_result();
      $num_of_rows = $result->num_rows;
      $result = $result->fetch_array();
      // If Serial is on verify DB (means that someone already registered using it)
      if ( ($num_of_rows<>0) and ($error<>1) )
      {
      echo "Invalid Serial";
      $error=1;
      }
      if ($error<>1)
      {
      // Check if Username or E-mail is not already taken
      $stmt = $con->prepare("SELECT * FROM acc WHERE username= ? OR email= ? LIMIT 1");
      $stmt->bind_param('ss', $user, $email);
      $stmt->execute();
      $result = $stmt->get_result();
      $num_of_rows = $result->num_rows;
      $result = $result->fetch_array();
      $stmt = $con->prepare("SELECT * FROM verify WHERE username= ? OR email= ? LIMIT 1");
      $stmt->bind_param('ss', $user, $email);
      $stmt->execute();
      $result2 = $stmt->get_result();
      $num_of_rows2 = $result2->num_rows;
      $result2 = $result2->fetch_array();
      if ( ($num_of_rows) or ($num_of_rows2) ) // If user or email already exists on verify and acc DB
      {
      if ( (strcasecmp($result['username'], $user) == 0) or (strcasecmp($result2['username'], $user) == 0) ) // If user already exists
      {
      echo "Username in use";
      }
      else if ( (strcasecmp($result['email'], $email) == 0) or (strcasecmp($result2['email'], $email) == 0) )// If e-mail is already taken
      {
      echo "Email in use";
      }
      }
      else
      {
      // Do registration
      $password = password_hash( $pw, PASSWORD_ARGON2ID, [
      'memory_cost' => $memory_cost,
      'time_cost' => $time_cost,
      'threads' => $threads,
      ]);
      $rand_id = random_int(-10000, 10000);
      $rand_secret = random_str(32);
      $stmt = $con->prepare("INSERT INTO verify (username, password, email, serial, rand_id, rand_secret) VALUES (?,?,?,?,?,?)");
      $stmt->bind_param('ssssss', $user, $password, $email, $serial, $rand_id, $rand_secret);
      $stmt->execute();
      $confirmation_code = hash_hmac('sha256', $rand_id, $rand_secret);
      $variables = array();
      $variables['User'] = $user;
      $variables['Serial'] = $serial;
      $variables['Code'] = $confirmation_code;
      $template = file_get_contents("template2.html");
      foreach($variables as $key => $value)
      {
      $template = str_replace('{{ '.$key.' }}', $value, $template);
      }
      $mail->addAddress($email, $user);
      $mail->msgHTML($template, __DIR__);
      $mail->AltBody = "You can activate your account here: www.mysite.com/activate.php?id=$serial&conf=$confirmation_code";
      if (!$mail->send())
      {
      echo 'error, mail not delivered';
      }
      else
      {
      echo "Registration OK! mail sent";
      }
      }
      }
      $stmt->close();
      $con->close();
      return;
      }

      function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
      {
      $pieces = ;
      $max = mb_strlen($keyspace, '8bit') - 1;
      for ($i = 0; $i < $length; ++$i) {
      $pieces = $keyspace[random_int(0, $max)];
      }
      return implode('', $pieces);
      }
      ?>


      activate.php



      <?php
      if ( ! empty( $_GET ) )
      {
      if ( isset($_GET['id']) and isset($_GET['conf']) )
      {
      $key=$_GET['id'];
      $code=$_GET['conf'];
      $con = new mysqli($host, $username, $password, $database);
      $stmt = $con->prepare("SELECT * FROM verify WHERE serial=?");
      $stmt->bind_param('s', $key);
      $stmt->execute();
      $result = $stmt->get_result();
      $num_of_rows = $result->num_rows;
      $result = $result->fetch_array();
      // If Serial doesn't exist or is already taken
      if ( ($num_of_rows==0) or ( empty( $result['username'] )) or ( empty( $result['rand_id'] )) or ( empty( $result['rand_secret'] )) )
      {
      $error=1;
      echo "Invalid code";
      }
      if ( $error<>1 )
      {
      $rand_id = $result['rand_id'];
      $rand_secret = $result['rand_secret'];
      $confirmation_code = hash_hmac('sha256', $rand_id, $rand_secret);
      if ($confirmation_code == $code)
      {
      $user=$result['username'];
      $password=$result['password'];
      $email=$result['email'];
      $stmt = $con->prepare("UPDATE accounts SET username = ?, password = ?, email = ? WHERE Serial = ?");
      $stmt->bind_param('ssss', $user, $password, $email, $key);
      $stmt->execute();
      $stmt = $con->prepare("DELETE from verify WHERE serial = ?");
      $stmt->bind_param('s', $key);
      $stmt->execute();
      echo "Account activated";
      }
      else
      {
      $error=1;
      echo "Invalid code";
      }
      }
      $stmt->close();
      $con->close();
      return;
      }
      }

      function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
      {
      $pieces = ;
      $max = mb_strlen($keyspace, '8bit') - 1;
      for ($i = 0; $i < $length; ++$i) {
      $pieces = $keyspace[random_int(0, $max)];
      }
      return implode('', $pieces);
      }
      ?>


      mail.php



      <?php
      use PHPMailerPHPMailerPHPMailer;
      require 'vendor/autoload.php';
      $mail = new PHPMailer;
      $mail->isSMTP();
      $mail->Host = 'smtphost';
      $mail->Port = port;
      $mail->SMTPAuth = true;
      $mail->Username = 'mymail';
      $mail->Password = 'mypassword';
      $mail->setFrom('mymail', 'myname');
      $mail->addReplyTo('mysupportmail', 'myname');
      $mail->AddEmbeddedImage('img/logo.png', 'mylogo');
      $mail->AddEmbeddedImage('img/gif.gif', 'mygif');
      $mail->Subject = "Activation for account";
      ?>


      Thanks in advance.










      share|improve this question









      New contributor




      Roberto Carlos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.







      $endgroup$




      I made an authentication system for my software (no browser is used) that works like this:




      • User receives a Key on it's mail after buying the software

      • User registers account using that key and inputs username, mail, password.

      • registration.php checks if the format is correct then sends an email with a confirmation code using a template and inserts the values into DB (verify table).

      • The HTML template contains username (from user input), 2 images from the same directory as the registration.php and the template2.html and a button with a get like this: "www.mysite.com/activate.php?id=$serial&conf=$confirmation_code".

      • activate.php checks if everything is valid and then inserts the verify table's row into accounts table. (after it is inserted into accounts table, it becomes a valid account).


      Everything works as expected (I'm still making the login.php).



      Why I'm posting this here:



      As a beginner (this is the first php, mysql and html thing I do) I know there is a lot of things that can be improved. My goal is to make it as secure as possible, I followed OWASP as much as I could and at the same time I read a lot on Stack Overflow and Security.Stackexchange.



      Here is my code:



      registration.php



      <?php
      $CurrentVersionHash="asd123";
      $CurrentVersion="alpha1";
      $CurrentHash="asd123";
      $serial_length="10";
      $memory_cost="31250";
      $time_cost="10";
      $threads="1";
      require 'mail.php';

      if ( ! empty( $_POST ) ) // Check if Post is not empty
      {
      $hash = $_POST['exe'];
      $pw = $_POST['pass'];
      $user = $_POST['name'];
      $version = $_POST['ver'];
      $email = $_POST['mail'];
      $confirm = $_POST['pass2'];
      $serial = $_POST['key'];
      if ( empty($hash) or empty($version))
      return;
      if ( $hash<>$CurrentHash) // Check if file Hash is valid
      {
      if ( $version<>$CurrentVersionHash ) // Check if it's because it's outdated
      {
      echo "Old exe";
      }
      else
      {
      echo "Exe not valid";
      }
      return;
      }
      // Checking if there is any problem in the format
      if ( (empty( $user )) or ( strlen( $user ) < 6 ) or ( strlen( $user ) > 254 ) or ( ! ctype_alnum ( $user )) )
      {
      echo "Error";
      return;
      }
      if ( (empty( $pw)) or (strlen($pw) < 10) or (strlen($pw) > 254) or (! preg_match("#[0-9]+#", $pw)) or ( !preg_match("#[a-z]+#", $pw )) or ( !preg_match("#[A-Z]+#", $pw )) )
      {
      echo "Error";
      return;
      }
      if ( $pw != $confirm or empty( $confirm ) )
      {
      echo "Error";
      return;
      }
      if (( empty( $email )) or ( ! filter_var($email, FILTER_VALIDATE_EMAIL)) or (strlen($email) > 254) )
      {
      echo "Error";
      return;
      }
      if ( ( empty( $serial ) ) or ( strlen( $serial ) <> $serial_length ) )
      {
      echo "Error";
      return;
      }
      // Check if Serial exists and is not already taken
      $con = new mysqli($host, $username, $password, $database);
      $stmt = $con->prepare("SELECT * FROM acc WHERE serial=?");
      $stmt->bind_param('s', $serial);
      $stmt->execute();
      $result = $stmt->get_result();
      $num_of_rows = $result->num_rows;
      $result = $result->fetch_array();
      // If Serial doesn't exist or is already taken
      if ( ($num_of_rows==0) or ( ! empty( $result['username'] )) )
      {
      echo "Invalid Serial";
      $error=1;
      }
      $stmt = $con->prepare("SELECT * FROM verify WHERE serial=?");
      $stmt->bind_param('s', $serial);
      $stmt->execute();
      $result = $stmt->get_result();
      $num_of_rows = $result->num_rows;
      $result = $result->fetch_array();
      // If Serial is on verify DB (means that someone already registered using it)
      if ( ($num_of_rows<>0) and ($error<>1) )
      {
      echo "Invalid Serial";
      $error=1;
      }
      if ($error<>1)
      {
      // Check if Username or E-mail is not already taken
      $stmt = $con->prepare("SELECT * FROM acc WHERE username= ? OR email= ? LIMIT 1");
      $stmt->bind_param('ss', $user, $email);
      $stmt->execute();
      $result = $stmt->get_result();
      $num_of_rows = $result->num_rows;
      $result = $result->fetch_array();
      $stmt = $con->prepare("SELECT * FROM verify WHERE username= ? OR email= ? LIMIT 1");
      $stmt->bind_param('ss', $user, $email);
      $stmt->execute();
      $result2 = $stmt->get_result();
      $num_of_rows2 = $result2->num_rows;
      $result2 = $result2->fetch_array();
      if ( ($num_of_rows) or ($num_of_rows2) ) // If user or email already exists on verify and acc DB
      {
      if ( (strcasecmp($result['username'], $user) == 0) or (strcasecmp($result2['username'], $user) == 0) ) // If user already exists
      {
      echo "Username in use";
      }
      else if ( (strcasecmp($result['email'], $email) == 0) or (strcasecmp($result2['email'], $email) == 0) )// If e-mail is already taken
      {
      echo "Email in use";
      }
      }
      else
      {
      // Do registration
      $password = password_hash( $pw, PASSWORD_ARGON2ID, [
      'memory_cost' => $memory_cost,
      'time_cost' => $time_cost,
      'threads' => $threads,
      ]);
      $rand_id = random_int(-10000, 10000);
      $rand_secret = random_str(32);
      $stmt = $con->prepare("INSERT INTO verify (username, password, email, serial, rand_id, rand_secret) VALUES (?,?,?,?,?,?)");
      $stmt->bind_param('ssssss', $user, $password, $email, $serial, $rand_id, $rand_secret);
      $stmt->execute();
      $confirmation_code = hash_hmac('sha256', $rand_id, $rand_secret);
      $variables = array();
      $variables['User'] = $user;
      $variables['Serial'] = $serial;
      $variables['Code'] = $confirmation_code;
      $template = file_get_contents("template2.html");
      foreach($variables as $key => $value)
      {
      $template = str_replace('{{ '.$key.' }}', $value, $template);
      }
      $mail->addAddress($email, $user);
      $mail->msgHTML($template, __DIR__);
      $mail->AltBody = "You can activate your account here: www.mysite.com/activate.php?id=$serial&conf=$confirmation_code";
      if (!$mail->send())
      {
      echo 'error, mail not delivered';
      }
      else
      {
      echo "Registration OK! mail sent";
      }
      }
      }
      $stmt->close();
      $con->close();
      return;
      }

      function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
      {
      $pieces = ;
      $max = mb_strlen($keyspace, '8bit') - 1;
      for ($i = 0; $i < $length; ++$i) {
      $pieces = $keyspace[random_int(0, $max)];
      }
      return implode('', $pieces);
      }
      ?>


      activate.php



      <?php
      if ( ! empty( $_GET ) )
      {
      if ( isset($_GET['id']) and isset($_GET['conf']) )
      {
      $key=$_GET['id'];
      $code=$_GET['conf'];
      $con = new mysqli($host, $username, $password, $database);
      $stmt = $con->prepare("SELECT * FROM verify WHERE serial=?");
      $stmt->bind_param('s', $key);
      $stmt->execute();
      $result = $stmt->get_result();
      $num_of_rows = $result->num_rows;
      $result = $result->fetch_array();
      // If Serial doesn't exist or is already taken
      if ( ($num_of_rows==0) or ( empty( $result['username'] )) or ( empty( $result['rand_id'] )) or ( empty( $result['rand_secret'] )) )
      {
      $error=1;
      echo "Invalid code";
      }
      if ( $error<>1 )
      {
      $rand_id = $result['rand_id'];
      $rand_secret = $result['rand_secret'];
      $confirmation_code = hash_hmac('sha256', $rand_id, $rand_secret);
      if ($confirmation_code == $code)
      {
      $user=$result['username'];
      $password=$result['password'];
      $email=$result['email'];
      $stmt = $con->prepare("UPDATE accounts SET username = ?, password = ?, email = ? WHERE Serial = ?");
      $stmt->bind_param('ssss', $user, $password, $email, $key);
      $stmt->execute();
      $stmt = $con->prepare("DELETE from verify WHERE serial = ?");
      $stmt->bind_param('s', $key);
      $stmt->execute();
      echo "Account activated";
      }
      else
      {
      $error=1;
      echo "Invalid code";
      }
      }
      $stmt->close();
      $con->close();
      return;
      }
      }

      function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
      {
      $pieces = ;
      $max = mb_strlen($keyspace, '8bit') - 1;
      for ($i = 0; $i < $length; ++$i) {
      $pieces = $keyspace[random_int(0, $max)];
      }
      return implode('', $pieces);
      }
      ?>


      mail.php



      <?php
      use PHPMailerPHPMailerPHPMailer;
      require 'vendor/autoload.php';
      $mail = new PHPMailer;
      $mail->isSMTP();
      $mail->Host = 'smtphost';
      $mail->Port = port;
      $mail->SMTPAuth = true;
      $mail->Username = 'mymail';
      $mail->Password = 'mypassword';
      $mail->setFrom('mymail', 'myname');
      $mail->addReplyTo('mysupportmail', 'myname');
      $mail->AddEmbeddedImage('img/logo.png', 'mylogo');
      $mail->AddEmbeddedImage('img/gif.gif', 'mygif');
      $mail->Subject = "Activation for account";
      ?>


      Thanks in advance.







      beginner php html mysql mysqli






      share|improve this question









      New contributor




      Roberto Carlos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.











      share|improve this question









      New contributor




      Roberto Carlos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      share|improve this question




      share|improve this question








      edited 1 min ago









      Ludisposed

      7,48921959




      7,48921959






      New contributor




      Roberto Carlos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      asked 26 mins ago









      Roberto CarlosRoberto Carlos

      62




      62




      New contributor




      Roberto Carlos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      Roberto Carlos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      Roberto Carlos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






















          0






          active

          oldest

          votes











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });






          Roberto Carlos is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f212134%2fauthentication-system-no-browser-with-mail-verification%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          0






          active

          oldest

          votes








          0






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          Roberto Carlos is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          Roberto Carlos is a new contributor. Be nice, and check out our Code of Conduct.













          Roberto Carlos is a new contributor. Be nice, and check out our Code of Conduct.












          Roberto Carlos is a new contributor. Be nice, and check out our Code of Conduct.
















          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f212134%2fauthentication-system-no-browser-with-mail-verification%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          404 Error Contact Form 7 ajax form submitting

          How to know if a Active Directory user can login interactively

          Refactoring coordinates for Minecraft Pi buildings written in Python