The simplest, fool-proof way, for all data types, of preventing sql special characters in a value from being able to break the sql query syntax, which is how sql injection is accomplished, is to use a prepared query. Because the mysqli extension is overly complicated and inconsistent, especially when dealing with prepared queries, this would be a good time to switch to the much simpler and more modern PDO extension.
As to the posted code -
- Don’t handle database errors in your code for non-recoverable errors. The only errors that are recoverable by the user are when inserting/updating duplicate or out of range values. This is the only time you should have error handling logic in your code. In fact, in the latest php versions, both the mysqli and PDO extensions always use exceptions for errors and the error handling logic you have now won’t ever be executed upon an error and should be removed.
- Your post method form processing code should detect if a form has been submitted before referencing any of the form data.
- You should keep the form data as a set, in a php array variable, then operate on elements in this array variable throughout the rest of the code, i.e. don’t write out lines of code copying variables to other variables for nothing.
- You should trim all input data, mainly so that you can detect if all white-space characters were entered.
- You should validate data before using it, storing validation errors in an array using the field name as the main array index.
- After the end of the validation logic, if there are no errors, use the submitted data.
- Don’t use a loop to fetch what will be at most one row of data. Just fetch the row and test if there was a fetched row.
- Again, don’t copy variables to other variables for nothing. Just use the original variables the fetched data is in.
- If the query matched a row, you know the WHERE … term was TRUE. There’s no good reason to test in your program logic if $jmeno == $db_jmeno. In fact, in your current code, if $jmeno is empty, which shouldn’t match any row, that part of the comparison will be TRUE (an empty value is equal to an empty value.)
- The logic testing if $heslo == password_verify($heslo, $db_heslo) is not technically correct. Password_verify returns either a true or false value. Comparing this with the value in $heslo doesn’t make sense (it does work, because the value in $heslo is a true value, which will match a true value from password_verify.)
- You should only store the user id (auto-increment primary index) in a session variable. You should query on each page request to get any other user data, such as the username, permissions, …
Edit: also, in the current program logic, without validation on the server-side and without testing if a row of data was matched, the if ($jmeno == $db_jmeno AND $heslo == password_verify($heslo, $db_heslo)){
logic, for both an empty $jmeno and empty $heslo value, will result in a true result, meaning that $_SESSION[‘loggedin’] will be set to a TRUE value. If this is what you are testing on your secure pages, someone can simply submit empty values to your login code, become logged in, and then can access any of the secured pages.