debugphpMinor
Registration form with validation and error messages
Viewed 0 times
errorregistrationwithvalidationmessagesandform
Problem
I've been working on a registration form page in jQuery Mobile and I think I'm starting to get it fully complete.
What I need feedback on is if I've forgotten anything in terms of accessibility, security (am I open to any SQL injections or other risks?) and just anything in general. It's my first time creating a proper registration form like this so surely there must be things to improve. The labels are in Swedish but you can see the translation in the
I would also like some feedback on my PHP validation because it doesn't feel very dry at all with all the
Form markup:
```
">
Förnamn:
"
placeholder="Förnamn" alt="Skriv ditt förnamn" data-clear-btn="true">
Efternamn:
"
placeholder="Efternamn" alt="Skriv ditt efternamn" data-clear-btn="true">
Gatuaddress:
" placeholder="T.ex. Kungsgatan 40"
alt="Skriv din gatuaddress" data-clear-btn="true">
Postnummer:
" placeholder="T.ex. 333 33"
alt="Skriv ditt postnummer" data-clear-btn="true">
Stad/ort:
"
placeholder="Stad/ort" alt="Skriv din stad eller ort"
data-clear-btn="true">
Utbildning:
Cobolutvecklare
Programvarutestare
Projektledare
Webbutvecklare
Ingen (admin, lärare)
Behörighetskod:
" placeholder="4 siffror (XXXX)"
alt="Skriv din behörighetskod" data-clear-btn="true">
Email:
"
placeholder="Exempel@jensen.se" alt="Skriv din e-post address"
data-clear-btn="true"
What I need feedback on is if I've forgotten anything in terms of accessibility, security (am I open to any SQL injections or other risks?) and just anything in general. It's my first time creating a proper registration form like this so surely there must be things to improve. The labels are in Swedish but you can see the translation in the
$validation echo. I would also like some feedback on my PHP validation because it doesn't feel very dry at all with all the
if statements. Perhaps you can combine them in some clever way? Form markup:
```
">
Förnamn:
"
placeholder="Förnamn" alt="Skriv ditt förnamn" data-clear-btn="true">
Efternamn:
"
placeholder="Efternamn" alt="Skriv ditt efternamn" data-clear-btn="true">
Gatuaddress:
" placeholder="T.ex. Kungsgatan 40"
alt="Skriv din gatuaddress" data-clear-btn="true">
Postnummer:
" placeholder="T.ex. 333 33"
alt="Skriv ditt postnummer" data-clear-btn="true">
Stad/ort:
"
placeholder="Stad/ort" alt="Skriv din stad eller ort"
data-clear-btn="true">
Utbildning:
Cobolutvecklare
Programvarutestare
Projektledare
Webbutvecklare
Ingen (admin, lärare)
Behörighetskod:
" placeholder="4 siffror (XXXX)"
alt="Skriv din behörighetskod" data-clear-btn="true">
Email:
"
placeholder="Exempel@jensen.se" alt="Skriv din e-post address"
data-clear-btn="true"
Solution
Security
First the good news: You use prepared queries which is a good thing as it prevents SQLInjection one of the most nasty and common security breaches. Also you escape output to prevent XSS.
But you should improve the following things:
Add a CSRF token!!, otherwise a new administrator can be added by performing a CSRF attack on one of your users that are allowed to add users. As a general rule add tokens to all forms that use
What do you do with the input variables before you pass them into
Do not use
Then you can write
Always escape all variables in your template files that are not supposed to contain html. Even if you have filtered them. So never write something like:
Always use
Structure
Your current validation functions is a bit hard to read. You can tidy it up a lot if you outsource some things to own functions, use a loop and
```
User exists.
* False -> User does not exists.
*/
function user_exits($email)
{
global $db;
$query = 'SELECT count(*) as c FROM useraccounts';
$query .= 'WHERE email = :email';
$prepared_stmt = $db->prepare($query);
$prepared_stmt->bindParam(':email', $email);
$prepared_stmt->execute();
$row = $prepared_stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row) || ($row['c'] prepare($query);
$prepared_stmt->execute();
$row = $prepared_stmt->fetch(PDO::FETCH_ASSOC);
// Swap keys and values.
return array_combine($row, array_keys($row));
}
/**
* Validate user input from registration form.
*
* @warning
* This function processes unfiltered user input!
*
* @param String $firstname
* The users first name.
*
* @param String $lastname
* The users last name.
*
* @param String $address
* Street and house number of the users residence.
*
* @param String $postal_code
* Swedish zip code of the users residence.
*
* @param String $city
* The city of the users residence.
*
* @param String $usertype
* The type of user, for example teacher.
*
* @param String $email
* The email of the user.
*
* @param String $password
* The users password in clear text.
*
* @param String $confirmed_password
* The repeated input of the users password in clear text.
*
* @return Array
* On error an Array with errormessages.
* On success an Array containing a success flag and the usertype as text.
*/
function validate_registration(
$firstname,
$lastname,
$address,
$postal_code,
$city,
$usertype,
$email,
$password,
$confirmed_password
) {
// Regexp to match text against wanted characters.
$text_regxp = '/^[A-Za-zéåäöÅÄÖ\s\ ]*$/';
$usertypes = get_user_types();
// Callbacks for filters.
$passwords_match = function() use ($password, $confirmed_password) {
return ($password == $confirmed_password);
};
$usertype_exists = function() use ($usertypes) {
return in_array($usertype, array_keys($usertypes));
};
$user_does_not_exists = function () use($email) {
return !user_exists($email);
};
$filters = array(
array(
'field' => 'firstname',
'var' => $firstname,
'filter' => FILTER_VALIDATE_REGEXP,
'filter_options' => array('regexp' => $text_regxp),
'error_msg' => 'Förnamnet kan endast innehålla bokstäver (é, a-ö) och mellanslag',
'required' => true,
'empty_msg' => 'Du måste ange ditt förnamn',
),
array(
'field' => 'lastname',
'var' => $lastname,
'filter' => FILTER_VALIDATE_REGEXP,
'filter_options' => array('regexp' => $text_regxp),
'error_msg' => 'Efternamnet kan endast innehålla bokstäver (é, a-ö) och mellanslag',
'required' => true,
'empty_msg' => 'Du måste ange ditt efternamn',
),
array(
'field' => 'address',
First the good news: You use prepared queries which is a good thing as it prevents SQLInjection one of the most nasty and common security breaches. Also you escape output to prevent XSS.
But you should improve the following things:
Add a CSRF token!!, otherwise a new administrator can be added by performing a CSRF attack on one of your users that are allowed to add users. As a general rule add tokens to all forms that use
method="post" and for all actions that need a user to be logged in (like log-out links).What do you do with the input variables before you pass them into
validation_registration? I recommend extracting your input variables from $_GET and $_POST in the same place where you validate them, so that you never pass around unnecessary dangerous input.Do not use
$_SERVER['PHP_SELF'] it's an unnecessary dangerous input variable which can be easily modified by an attacker. Instead define a constant base path in your config file like this:define('BASE_URL', '//example.com');Then you can write
/subdir in your template files. Always escape all variables in your template files that are not supposed to contain html. Even if you have filtered them. So never write something like:
echo $validation['email'];.Always use
htmlspecialchars with ENT_QUOTES because else ' will not be escaped: very dangerous! For easy use it's best to define a very short global function that will escape your values properly, like this:/**
* Escape given input for the use in HTML.
*
* @param String $input
* Unescaped input.
*
* @return String
* Escaped input.
*/
function e($input)
{
// Use htmlspecialchars with ENT_QUOTES to escape '.
return echo htmlspecialchars($variable, ENT_QUOTES, 'UTF-8');
}Structure
Your current validation functions is a bit hard to read. You can tidy it up a lot if you outsource some things to own functions, use a loop and
filter_var like this:```
User exists.
* False -> User does not exists.
*/
function user_exits($email)
{
global $db;
$query = 'SELECT count(*) as c FROM useraccounts';
$query .= 'WHERE email = :email';
$prepared_stmt = $db->prepare($query);
$prepared_stmt->bindParam(':email', $email);
$prepared_stmt->execute();
$row = $prepared_stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row) || ($row['c'] prepare($query);
$prepared_stmt->execute();
$row = $prepared_stmt->fetch(PDO::FETCH_ASSOC);
// Swap keys and values.
return array_combine($row, array_keys($row));
}
/**
* Validate user input from registration form.
*
* @warning
* This function processes unfiltered user input!
*
* @param String $firstname
* The users first name.
*
* @param String $lastname
* The users last name.
*
* @param String $address
* Street and house number of the users residence.
*
* @param String $postal_code
* Swedish zip code of the users residence.
*
* @param String $city
* The city of the users residence.
*
* @param String $usertype
* The type of user, for example teacher.
*
* @param String $email
* The email of the user.
*
* @param String $password
* The users password in clear text.
*
* @param String $confirmed_password
* The repeated input of the users password in clear text.
*
* @return Array
* On error an Array with errormessages.
* On success an Array containing a success flag and the usertype as text.
*/
function validate_registration(
$firstname,
$lastname,
$address,
$postal_code,
$city,
$usertype,
$email,
$password,
$confirmed_password
) {
// Regexp to match text against wanted characters.
$text_regxp = '/^[A-Za-zéåäöÅÄÖ\s\ ]*$/';
$usertypes = get_user_types();
// Callbacks for filters.
$passwords_match = function() use ($password, $confirmed_password) {
return ($password == $confirmed_password);
};
$usertype_exists = function() use ($usertypes) {
return in_array($usertype, array_keys($usertypes));
};
$user_does_not_exists = function () use($email) {
return !user_exists($email);
};
$filters = array(
array(
'field' => 'firstname',
'var' => $firstname,
'filter' => FILTER_VALIDATE_REGEXP,
'filter_options' => array('regexp' => $text_regxp),
'error_msg' => 'Förnamnet kan endast innehålla bokstäver (é, a-ö) och mellanslag',
'required' => true,
'empty_msg' => 'Du måste ange ditt förnamn',
),
array(
'field' => 'lastname',
'var' => $lastname,
'filter' => FILTER_VALIDATE_REGEXP,
'filter_options' => array('regexp' => $text_regxp),
'error_msg' => 'Efternamnet kan endast innehålla bokstäver (é, a-ö) och mellanslag',
'required' => true,
'empty_msg' => 'Du måste ange ditt efternamn',
),
array(
'field' => 'address',
Code Snippets
define('BASE_URL', '//example.com');/**
* Escape given input for the use in HTML.
*
* @param String $input
* Unescaped input.
*
* @return String
* Escaped input.
*/
function e($input)
{
// Use htmlspecialchars with ENT_QUOTES to escape '.
return echo htmlspecialchars($variable, ENT_QUOTES, 'UTF-8');
}<?php
/**
* Check if a user with given e-mail exists.
*
* @param String $email
* The e-mail to id the user.
*
* @return Boolean
* True -> User exists.
* False -> User does not exists.
*/
function user_exits($email)
{
global $db;
$query = 'SELECT count(*) as c FROM useraccounts';
$query .= 'WHERE email = :email';
$prepared_stmt = $db->prepare($query);
$prepared_stmt->bindParam(':email', $email);
$prepared_stmt->execute();
$row = $prepared_stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row) || ($row['c'] < 1)) {
return false;
} else {
return true;
}
}
/**
* Get a list of possible user types.
*
* Matches usertype id to readable user type.
*/
function get_user_types()
{
global $db;
// Get one row from usertypes table and extract field names.
$query = 'SELECT * FROM usertypes';
$prepared_stmt = $db->prepare($query);
$prepared_stmt->execute();
$row = $prepared_stmt->fetch(PDO::FETCH_ASSOC);
// Swap keys and values.
return array_combine($row, array_keys($row));
}
/**
* Validate user input from registration form.
*
* @warning
* This function processes unfiltered user input!
*
* @param String $firstname
* The users first name.
*
* @param String $lastname
* The users last name.
*
* @param String $address
* Street and house number of the users residence.
*
* @param String $postal_code
* Swedish zip code of the users residence.
*
* @param String $city
* The city of the users residence.
*
* @param String $usertype
* The type of user, for example teacher.
*
* @param String $email
* The email of the user.
*
* @param String $password
* The users password in clear text.
*
* @param String $confirmed_password
* The repeated input of the users password in clear text.
*
* @return Array
* On error an Array with errormessages.
* On success an Array containing a success flag and the usertype as text.
*/
function validate_registration(
$firstname,
$lastname,
$address,
$postal_code,
$city,
$usertype,
$email,
$password,
$confirmed_password
) {
// Regexp to match text against wanted characters.
$text_regxp = '/^[A-Za-zéåäöÅÄÖ\s\ ]*$/';
$usertypes = get_user_types();
// Callbacks for filters.
$passwords_match = function() use ($password, $confirmed_password) {
return ($password == $confirmed_password);
};
$usertype_exists = function() use ($usertypes) {
return in_array($usertype, array_keys($usertypes));
};
$user_does_not_exists = function () use($email) {
return !user_exists($email);
};
$filters = array(
array(
'field' => 'firstname',
'var' => $firstname,
'filter' => FILTER_VALIDATE_REGEXP,
'filter_options' => array('regexp' => $text_regxp),
'error_msg' => 'Förnamnet kan endast innehålla bokstäver (é, a-ö) och mellanslag',
'required' =>Context
StackExchange Code Review Q#77165, answer score: 4
Revisions (0)
No revisions yet.