Let's say I have a SQL statement like this that checks a user login:
SELECT * FROM users
WHERE username='test#example.com', password='abc123', expire_date>=NOW();
Is there a way in SQL to determine specifically which WHERE conditions fail, without having to separate each condition into its own query and test individually?
In this specific example it would allow the developer to tell users exactly the reason why their attempt to login failed.
For my purposes I'm using PHP/MySQL.
Well, one thing you could do is change your query so it only matches on the username. Then in the code you check the password and the expiration date, returning appropriate errors.
Also, I hope your example query is a simplification; Certainly you should be salting/encrypting/hashing your passwords, and you should include something that limits the number of failed attempts within a certain timeframe, etc...
As far as your actual question (as opposed to the results you are looking for), there isn't a way to get that information from the where clause. The closest you could do would be something like:
SELECT *,
CASE WHEN Password = 'asdf' THEN 1 ELSE 0 END AS IsPasswordMatch,
CASE WHEN Expiration >= NOW() THEN 1 ELSE 0 END AS IsActiveAccount
FROM Users
WHERE Username = 'user'
In MySQL you can put boolean expressions in the select-list. Boolean expressions evaluate to the integer 1 when true, or the integer 0 when false.
SELECT password = 'abc123' AS is_authenticated,
expire_date >= NOW() AS is_not_expired
FROM users
WHERE username='test#example.com';
note: If you need to write a query that works on other brands of RDBMS, keep in mind this use of boolean expressions is nonstandard. Use the CASE syntax that other folks have posted.
PS: This is a tangent from your question, but I urge you not to store passwords in plaintext. Store a hash digest of the salted password. See How does password salt help against a rainbow table attack?
No, the where-clause is applied as a block, and various techniques are used so that not all rows have to be scanned. Also, how would you know which row was the one that was desired?
Additionally, you probably don't want to tell the user too much about why a login attempt failed. Saying too much allows for exploits such as account mining and password attacks.
edit If you truly do want to display this to your user, then split your logic into different parts:
Validate identity
Action: Fetch the corresponding user row from the database
Result:
If no such row exist => invalid account
If row is returned, continue to step 2.
Validate credential
Action: Check the stored credential (password, hash of password or encrypted password) against the supplied password treated in the same way the credential is stored.
Result:
No match => Invalid password / credential
Match => Successful login attempt
Login user
Action: Add data to session etc.
You probably just need to separate the parts of the where clause with 'AND'
SELECT * FROM users
WHERE username='test#example.com'
And password='abc123'
And expire_date>=NOW();
Here is what I came up with:
SELECT
IF(mem_username='test#example.com','true','Error: Bad Username') AS mem_username,
IF(mem_password ='abc123','true','Error: Bad Password') AS mem_password'
FROM MEMBERS
WHERE mem_username='test#example.com' AND mem_password='abc123'
This way I can detect in code for error messages and then display them as necessary.
NOTE: To all of you citing security concerns with the example code, thank you for your concern. However, this is not real production code this was simply a simple example to demonstrate the question I had. It is quite obvious that you shouldn't store passwords in clear text, and that you shouldn't give users specifics on why login fails.
Related
This is probably a repeated question but I really can't find any answers. I have a forgotten password script which grabs the users email address from the URL using $_GET. The script then checks the $_GET for error messages then passes through to mysql to check weather the user id is the same.
Now the script itself is fine but the user id comes back as a string instead of an integer in var_dump(). I was going to let it go but then I read that if the user id is not an integer in a $_GET variable it could lead to an attack, which has got me a little bit worried. I have tried to change the num_rows value to an int but with no success, because num_rows returns an array. The code that checks the user id is:
if (mysql_num_rows($result) == 1) {
// NEED TO TURN THIS ARRAY TO AN INT
list($userId) = mysql_fetch_array($result, MYSQL_NUM);
}
//elseif (mysql_num_rows($result) <= 0) {
else {
$wrong = '<p style="color: red">Something went wrong. Please try again</p>';
}
Like I said before the script itself is fine, it's just that I read something and now can't get it out of my head, which has lead me to ask you guys. Doe's a user id have to be an integer or can i get away with it as a string?
EDIT: Thanks for all your comments and suggestions. There's still a lot to learn in php. Cheers again.
The user ID doesn't have to be an integer, however it is usually
unique (one per user)
formatted a way that it is searchable efficiently in a database (for instance)
short so that it doesn't take much space, especially for large users bank
therefore, ideally, that would be an integer (which number of bits must cover the largest number of expected users)
unique - for instance allocated automatically (incrementally) by a database
efficiently indexed, and being the small pointer to a larger user table
short
one of the drawbacks is that if the integer is given to the client browser (eg in a form), the client may guess the number of user / change the form user ID since numbers in sequence can be easily guessed... (that needs to be prevented via further security)
Everything you pull from the $_GET array is going to be a string, owing to the fact that there's no type information sent through http.
You should, however, always be sanitizing user input or using prepared statements to prevent SQL injection attacks.
You mixed things up, I'll try to clarify them. When PHP receives data via HTTP protocol, be it GET or POST - all the data is considered to be a string because there is no way to safely identify what the data should be.
That means you can try to coerce a piece of data you got via $_GET or $_POST into a different type internally, using typecasting, such as $id = (int)$_GET['id'];
As for databases, the id (let's call it Primary Key) should always be an integer.
Why:
- InnoDB uses a certain principle internally when it comes to actual data-structure organisation and writing the information to the disk. It accomplishes a significant performance gain by using sequentially incremented integers
- It's easy to create unique identifiers using integers. Just increment some number by an offset every time you do an insert and you get a unique number assigned to the row, no need for a complex algorithm that calculates unique identifier such as UUID() etc.
Long story short - keep the user's ID as integer in MySQL and if you receive data via $_POST/$_GET - type cast them into what you want them to be (int, float, string etc.)
A user ID can be anything you want it to be ... I would only suggest that it be something that has a unique key on it in the database and doesn't change over time.
ID's are traditionally used because it is good practice to have an auto-incrementing primary key on your tables anyway and it meets all of the criteria to be an effective user id.
I am looking for the best way to write out a php/mysql query to create unique user id's rather than using the autoincrement method in mysql.
Ex: Facebook gives users a long string of numbers as a user id when singing up before you can assign a username. This string of numbers can be used to view your profile OR you can use username. I want users to be able to change username in the future, so don't want to design my system based on username.
I don't know how big the site will get, so please take that into consideration with the solution. I don't want something that is going to be server intensive if there are alot of users signing up.
There isn't really a best route for something like this. Essentially you need to ask yourself what your system requires. You may be able to use an email address as the ID, an auto-incremented number, MD5 hash, or even a heavy-entropy GUID.
Keep in mind that email addresses may change, auto-incremented numbers can be leveraged in automated exploits, and there's technically some chance of hashes colliding.
If you decided to go the route of generating a high-entropy GUID using PHP, you could do so using a function like uniqid.
echo uniqid(); // 513ac40699d85
echo uniqid("_", true); // _513ac3e00bfe46.78760239
The second line shows the two arguments you can provide; a prefix, and a request for more entropy, which will result in a more unique result.
You should follow some algorithm like this:
Enter your new user into the database.
Get the record ID
Generate the userID
Insert the userID next to the name into the sql database.
Enter your new user into the database.
//get username from previous form
$user=$_POST['user'];
// login into mysql server and prepare data for writing
$connect=mysql_connect('localhost', $user, $pass);
$selectdb = mysql_select_db('mydb');
$query = "insert into users_table set
username='$user';";
$run_query=mysql_query($query);
Get the record ID
$id=mysql_insert_id();
Generate the userID
$first_chars=substr($user, 2);
$year=date('y');
$new_user_id= $first_chars.$year.$id;
Insert the UserID next to the name into the sql database
$query="update users_table set userid='$new_user_id' where id='$id';";
$run_query=mysql_query($query);
if (!$run_query) {
echo mysql_error();
}
else {
echo 'your user name is '.$user.' and user id is '.$new_user_id ; }
You can use mysql as a database. Wampserver combines everything and makes it easy. However, i'm not sure if I can help you very much because your question is very vague. Add some more detail please.
Use a hexdigest like sha or md5 to generate an id something like sha1($uname+$timestamp+$salt)
By doing this your will be storing a lot of data for each entry as sha1 takes up 40 bytes.You have already mentioned that the site is may go big,making it a huge amount of data.Decide whether its worth that lot of space.
PS:you can always slice the string,but the collision chance is more that way.
Hi everyone I want make sure the username is unique!
First I get there username and check if existing already. If not add them to the database, but if the username is taken I will add number behind it.
Example: JoshSmith, JoshSmith1
If there is no username the user still use the id version. I will get there first and last name. And then repeat the above steps.
The problem: If JoshSmith is taken I will add number behind it, but if JoshSmith1 is also taken.
If I use
SELECT count(*) FROM users WHERE username LIKE '%JoshSmith%'
it will return names like JoshSmithing and this is wrong. If I use
MATCH () AGAINST ()
returns the same results.
First I thought to count how many times the username exist and add the number+1
JoshSmith14
But that will be wrong.
You can use regular expressions to filter JoshSmith[numbers] and find biggest number. But, as for me, this is bad way, as you will have a lot of loading to your DB server. I think that it would be better to store counts in some table (especially if correct numbers are important to you).
I've been dabbling abit with a login script (PHP and MySQL), using various online tutorials. Today I became curious as to which way is the best to check if a username and password is valid. Every tutorial I've read so far checks the number of row returned, like this:
SELECT stuff FROM users WHERE username = input_username AND password = input_password
count the rows returned
if rows equal to one
login
else
display error message
I was just thinking if it's equally viable to use COUNT and check that value instead, like this:
SELECT COUNT(stuff) FROM users WHERE username = input_username AND password = input_password
if returned value equal to one
login
else
display error message
I've tried both and both work return the intended result. So is there any reason to choose one over the other?
They are both valid approaches. You would use the former when you needed additional info about the user, e.g., their UserID.
You should definitely go with second SQL-statement if you don't need any of the information stored. COUNT() is better for performance. Although the reason people tend to stick to the first one is because they will be needing information about the user if rows returned is positive.
I'm having a dilemma. I have a field hashedX that is a hashed/salted value and the salt is saved in the same row in the mysql database as is common practice.
hashedX saltX
------ ------
hashed1 ssai3
hashed2 woddp
hashed3 92ofu
When I receive inputX, I need to know if it matches any of the values in hashedX such as hashed1 hashed2 or hashed3. So typically I would take my input, hash/salt it, and compare it to the values of hashedX. Pseudo code:
$hashed_input = hash ($input with $salt );
select * from tablename where $hashed_input is hashedX
The problem is I don't know which saltX I need to even get to the $hashed_input before I can do any select.
I could go through the database rows, one by one, try that salt on my input, then check if the input as hashed/salted with this salt matches hashedX of that same row. If I have a 100,000 records, my guess is that this would be painfully slow. I have no idea how slow since I'm not that great at databases.
Is there a better way to do this, than selecting all rows, looping through them, using that row's salt to hash input, then comparing again to the hashed value in the db?
If it is possible (depends on your hash formula) define a MySQL User Defined Function database side for the hash formula (see CREATE FUNCTION). This way you will be able to get your results in one simple request:
SELECT hashedX, saltX FROM tablename WHERE UDFhash(input, saltX) = hashedX ;
You don't specify which hash algorithm you're using in PHP. MySQL supports MD5 and SHA1 hash algorithms as builtin functions:
SELECT ...
FROM tablename
WHERE SHA1(CONCAT(?, saltX)) = hashedX;
SHA2 algorithms are supported in MySQL 5.5, but this is only available in pre-beta release at this time. See http://dev.mysql.com/doc/refman/5.5/en/news-5-5-x.html for releases.
Is there a better way to do this, than selecting all rows, looping
through them, using that row's salt to
hash input, then comparing again to
the hashed value in the db?
Yes. A much better way.
Typically a salt is only used to prevent exactly what you are trying to do. So either you don't want to use a salt, or you don't want to do this kind of lookup.
If you are checking an entered password against a given user account or object, you should reference the object on the same line that you have the salt and hashed salt+password. Require the account name / object to be referenced when the password is given, then look up the row corresponding to that account name and object and compare the password against that salt + hash.
If you are keeping a record of items that you've seen before, then you should just go with a hash, (or a bloom filter) and forget the salt, because it doesn't buy you anything.
If you're doing something new / creative, please describe what it is.