Username correct, password incorrect? - php

In a login system, how can you tell if the user has entered the password incorrectly? Do you perform two SQL queries, one to find the username, and then one to find the username and matching (salted+hashed etc) password? I'm asking this because If the user entered the password incorrectly, I want to update the failed_login_attempts column I have.
If you perform two queries wouldn't that increase overhead?
If you did a query like this, how would you tell if the password entered was correct or not, or whether the username doesn't exist:
SELECT * FROM author
WHERE username = '$username'
AND password = '$password'
LIMIT 1
( ^ NB: I'm keeping it simple, will use hash and salt, and will sanitize input in real one.)
Something like this:
$user = perform_Query() // get username and password?
if ($user['username'] == $username && $user['password'] == $password)
{
return $user;
}
elseif($user['username'] == $username && $user['password'] !== $password)
{ // here the password doesn't match
// update failed_login_attemps += 1
}

You're overthinking it. Only one query is required:
SELECT * FROM author WHERE username = '$username';
Then do:
if ($user['password'] == saltedHash($password,$user['salt'])) {
return "successful";
}
else {
return "failed";
}
Username must be unique. Otherwise this won't work. I would advise against making username non-unique because it causes a lot of other headaches apart from this.

If you perform two queries wouldn't that increase overhead?
I'd say it doesn't matter really. Many complex web frameworks issue dozens or hundreds of queries per request. One more or less won't change things much.
I think it's really up to preference. Fetching the whole user row, and then checking the password on PHP side makes the most sense as far as I can see, because you then already have the ID you need to update the failed_logins column.

What we do is perform one query to find the user (based on userid) and we select the UserId, PwSalt, and PwHash.
If no user is found then we know it is an invalid username.
If the user is found, we hash the password and compare it to the pwHash from the query. If the hash doesn't match we update the failed login attempts.

In your code, $user will be empty if username or password is incorrect
SELECT password = '$password' AS is_legit, *
FROM author
WHERE username = '$username'
LIMIT 1
$user = perform_Query() // get username and password?
// $user will be empty if the username is incorrect
$user_exists = $user.length > 0;
// to make sure we don't address a
// non-existent array element
if($user_exists && $user['password'] == $password){
return $user;
}
elseif($user_exists && $user['password'] !== $password)
{ // here the password doesn't match, but the user does
// update failed_login_attemps += 1
// be sure to let the user know that the penetration
// attempt is halfway complete
}
else{
// F4il
}

The way (and many others) create login systems is like so:
On Registration
create a unique hash and store along with username,password
On Login
Pull username,password,hash from database
Use the clause WHERE username = '$username'
If theres 1 row, the username is correct
build a compiled hash with hash($post_pass,$user_hash) and compare with $user_pass
Also if you return anything at 1 point of your method anything after would not be run, so
if ($user['username'] == $username && $user['password'] == $password)
{
return $user;
}
elseif($user['username'] == $username && $user['password'] !== $password)
{ // here the password doesn't match
// update failed_login_attemps += 1
}
can be modified to
if ($user['username'] == $username && $user['password'] == $password)
{
return $user;
}
return false
because if ($user['username'] == $username && $user['password'] == $password) is met, then the return would be executed there for the false would not be executed.
Hope this helps.

Related

PHP - Check If Username Exists Or If Submitted Username Is Current

I'm trying to check if the entered username already exists or if the entered username is the current username.
I've Googled various SO questions but none seem to check if the current username is the submitted one.
The problem with the following code; it doesn't matter if the username is taken or not, it will still let you save.
$stmt = $engine->runQuery("SELECT user_name, user_email FROM users WHERE user_name=:username OR user_email=:email");
$stmt->execute(array(':username'=>$username, ':email'=>$email));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if(strtolower($row['user_name']) == strtolower($username) || $username !== $row['user_name']) {
$engine->authapi(false, 'Sorry, username is already taken. Please choose a different one.');
} elseif(strtolower($row['user_email']) == strtolower($email) && $email !== $_SESSION['user_email']) {
$engine->authapi(false, 'Email is already registered. You cannot use the same emails for multiple accounts.');
} else {
// save
}
How can I make it so it checks if the username is taken or not, and at the same time check if the submitted username is the current username (if so, let the user save)?
Actually, there are several issues in your code.
1) Your SQL. You can fetch more than one row here, for example you have two entries in your database, username: maio290, e-mail: a#foo.bar and username: maio291, e-mail: b#foo.bar. Now your user enteres username: maio290 and e-mail: b#foo.bar which will result in two entries selected. Most likely an edge case, but a valid one.
2) Your if: You're comparing strtolower($row['user_name']) == strtolower($username) OR $username !== $row['user_name']) - the second one doesn't make any sense with your error. Since that means: "hey, your user is not in our database, please take a different one" Also, the first comparision could be a lot nicer with using strcasecmp.
I would really split these two options, since it's a lot better to read and you don't have the problem with two selectable rows. Also, you let your database handle the comparision.
Therefore I would write the code like that:
<?PHP
// Select if username is taken
$stmt = $engine->runQuery("SELECT user_name FROM users WHERE user_name=:username");
$stmt->execute(array(':username'=>$username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if(count($row) != 0)
{
$engine->authapi(false, 'Sorry, username is already taken. Please choose a different one.');
// I would actually return here, so we wouldn't need an else
}
else
{
// check if e-mail is registred
$stmt = $engine->runQuery("SELECT user_email FROM users WHERE user_email=:email");
$stmt->execute(array(':email'=>$email));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if(count($row) != 0)
{
$engine->authapi(false, 'Email is already registered. You cannot use the same emails for multiple accounts.');
}
else
{
// store
}
}
?>

Which method is better to validate user credentials?

Should I validate a username and pass word by searching for both in the SQL table Or Should I find the username then match the pass word with a PHP if statement?
SELECT * FROM table WHERE username = $username AND password =$password
SELECT * FROM table WHERE username = $username
...
if ($row[password] == $password)
{
do stuff
}
Which method Is more secure and efficient?
The thing is… You are supposed to store salted, hashed passwords in the database. Since these are individually salted per user/password, you cannot look them up directly with password = ?, because you don't know the salt and therefore cannot calculate the matching hash in advance. If you're doing this properly, you must fetch the user record by username first, then validate the password hash using the retrieved salt/hash. Pseudocode:
$user = fetch_from_database($_POST['username']);
if (!$user) {
throw new Exception("User doesn't exist");
}
if (!password_verify($_POST['password'], $user['password_hash'])) {
throw new Exception('Invalid password');
}
echo 'Welcome ', $user['name'];
See http://php.net/password_hash, http://php.net/password_verify.
From the 2 listed above, the second one is more secure, because first one is more tilted towards SQL injection.
SELECT * FROM table WHERE username = $username AND password =$password
In this code if the value of username and password entered is something like "a or ('a'='a')"
the code will be modified to
SELECT * FROM table WHERE username = a or ('a' = 'a') AND password = a or ('a' = 'a')
Which means a clear code for listing all your data.
Whereas in the second case , IF condition will consider the value as a single string only. So second is the best among the 2 u mentioned..
Hope this helps

Changing function to check for username || password

I have the following code that checks for both the username and password but I would like to change it so that it checks to see if the username or password is valid.
I was thinking of making two different functions checkUsername and checkPassword as functions and have two call backs for each input.
I just would like to know if I am on the right track or if my controller and model can be altered how they are?
Model:
function check_login($username,$password) {
$query = $this->db->query("SELECT id, first_name, last_name, email, password FROM users WHERE email = ? and password = ?", array($username, md5($password))); // Result
return ($query->num_rows() == 1) ? $query->row() : FALSE;
}
}
Controller:
function _checkUsernamePassword() {
// adding the _ makes the function 'private' so it can't be called from the URI.
extract($_POST); // Gets data from form and creates vars
$user = $this->login_model->check_login($username,$password);
if(! $user){ // != If username or password are not correct
$this->session->set_flashdata('login_error',TRUE); //does not add the non valid login to the session
$this->form_validation->set_message('_checkUsernamePassword', 'Sorry %s is not correct.');
return FALSE;
} else {
$this->session->set_userdata('logged_in',TRUE);
$this->session->set_userdata('user_id',$user->id);
$this->session->set_userdata('user_name',$user->first_name);
$this->session->set_userdata('user_email',$user->email);
return TRUE;
}
Will you ever have a scenario where you want to determine if a user name exists on its own? If not, I see no reason to add the extra functions. It's generally considered a bad practice to let the client know that a username was valid if an invalid password was submitted. It lets a malicious person know that they've solved 50% of the puzzle, freeing them to crack the password only. Further, I can't imagine a scenario where you'd want to check if a password exists on its own.
One thing I would note from your code is that md5 has been broken and sha1 is really a better option for storing passwords these days. Otherwise, without poring over the code looking for tiny improvements I'd say that you've used prepared statements with your inputs and everything else looks okay, so you're probably fine.
Easiest option: change the model, so you can specify whether to check for one or both.
function check_login($username,$password, $match_both = true) {
$query = $this->db->query("SELECT id, first_name, last_name, email, password FROM users WHERE email = ? " . ($match_both ? 'AND' : 'OR') . " password = ?", array($username, md5($password))); // Result
return ($query->num_rows() == 1) ? $query->row() : FALSE;
}
Yes better to check for user name first then go for passwrd check.
that way it will be more secure.
and use mysql_real_escape_string() to esacape special characters in the inputted username and password. else theere will be a way for sql injection.

How Can I Make This Login System More Secure

I have created this php login script. I was wondering weather it was secure and if not how could I improve it.
PHP Script
<?php
include_once ("ConnectToMySql.php");
session_start();
$username = $_POST['username'];
$username = mysql_real_escape_string($username);
$password = $_POST['password'];
$password = sha1($password);
$query = "SELECT password FROM users WHERE username = '$username';";
$result = mysql_query($query);
if(mysql_num_rows($result) < 1)
{
echo "This Username Is Not Registered!";
exit;
}
if(mysql_num_rows($result) == 1)
{
if ($password == $result)
{
echo "Logged In!";
}
else echo "Wrong Password!";
}
?>
Thanks
A first tip could be to show a common error for both invalid login cases: invalid username or password. That way an eventual attacker wouldn't know if the username is valid or not.
You could also make a single query matching both username and password. You would probably need more user information (to store in session?), so it would be a good idea to select those fields instead of the password (e.g. id, name).
Regarding the hashed password stored in the database, you could add a SALT to improve security.
http://en.wikipedia.org/wiki/Salt_%28cryptography%29
What I would do is change the query to the following:
"SELECT COUNT(*) FROM users WHERE username = '$username' AND password='$password';"
That way, you don't have to check if the password is correct afterwards (and you don't have to pass the sensitive data as well), you only have to check if the numbers of rows returned equal 1 and you can produce a single error message for both username/password.

Are there any security vulnerabilities in this PHP registration code?

Can you experts give me some thougths on this code? Some security hole i have missed?
Can you see any potential threats? Something i can do better?
I'm still learning :) Thanks
<?php
if (isset($_POST['username'])) {
$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string($_POST['password']);
$password2 = mysql_real_escape_string($_POST['password2']);
$encrypted_password = md5($password);
// remove eventuakl space
foreach($_POST as $key => $val) $_POST[$key] = trim($val);
// check if username is taken
$query = mysql_query("SELECT COUNT(*) FROM users WHERE username = '$username'");
if (mysql_result($query, 0) > 0) {
$reg_error[] = 0;
}
// make sure username only cosist of at least 3 letters, numbers or _ -
if (!preg_match('/^[a-zA-Z0-9_-]{3,}$/', $username)) {
$reg_error[] = 4;
}
// check for empty fields
if (empty($username) || empty($password) || empty($password2)) {
$reg_error[] = 2;
}
// check if the passwords match
if ($password != $password2) {
$reg_error[] = 3;
}
// save if error is unset
if (!isset($reg_error)) {
mysql_query("INSERT INTO users (username, password, registered, registration_ip)
VALUES('$username', '$encrypted_password', '".time()."', '".$_SERVER['SERVER_ADDR']."')");
$_SESSION['id'] = mysql_insert_id();
header('refresh: 3; url=/home');
}
}
?>
Login.php
if (isset($_POST['username'])) {
$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string($_POST['password']);
$md5_password = md5($password);
$query = mysql_query("SELECT id FROM users WHERE username = '$username' and password = '$md5_password'");
if (mysql_num_rows($query) == 0) {
header("Location: ".$_SERVER['REQUEST_URI']."");
exit;
}
// set session
$_SESSION['id'] = mysql_result($query, 0, 'id');
header("Location: /");
exit;
You didn't salt the password.
Also, md5() is considered not strong enough for hashing passwords.
Use hash('sha256', $password) instead.
I assume you're serving this on https, though you don't mention whether you do -- if you're not, username and password travel on the open net in the clear, and that's definitely not very safe.
There's a race condition -- you check whether the username is taken, first, and only later do you insert it. You should use a transaction, a least, and ideally just try to insert (with the uniqueness constraint imposed by the database) and catch the error in case of duplicates. And, you should do this only after all other sanity checks, i.e. when you've convinced yourself that, apart from possible duplicates, the registration attempt is OK.
Little bobby tables will give you a lot of headaches since you do not check for username validity.
You need to salt the password.
This is placed in the wrong place. Move it up a few lines before the $_POST vars are used.
// remove eventuakl space
foreach($_POST as $key => $val) $_POST[$key] = trim($val);
You are escaping the password fields for no reason. They are not being sent to the database. md5($password) is going to the database and it is not escaped.
EDIT: On the login side, you should be trimming anything you are trim on the registrations side.
Your error checking is out of order. I'd do the error checking in this order:
Check for empty fields
Check that the fields have valid values (filtering input)
Escape the fields with mysql_real_escape_string before using the fields in SQL
Check for the user in the SQL table
If you find an error don't continue with further checks. Guard each error check similar to your guard on the final INSERT statement.
You have no edits on the password fields besides using mysql_real_escape_string?
You should do mysql_connect before using mysql_real_escape_string. mysql_real_escape_string will use the connection to determine the character set of the connection. The character set will identify which characters to escape.
You should use parameters instead of building your sql dynamically. It will help prevent sql injection attacks. Little bobby tables will get you.

Categories