Untainted auth php. HTTP Setting page security using MySQL and PHP

Just yesterday, I decided to start developing a personal account of an unnamed site. I had a question about the appearance of the authorization form, frankly, I didn’t want to design the form, make pop-up hints, and generally pay great attention to the form. I foolishly rushed to look for ready-made solutions, I saw enough tasteless forms. I thought about looking for some ready-made component, a ready-made extension, but for the most part they disappointed me. Wandering around the forums, I found it interesting to play the card with HTTP Authentication: Basic. I went to read the manual, before I was not sufficiently aware of this method. Further, the problems began.

There was nowhere to go, I wandered around the net in search of sites that would undertake to generate forms, code, styles. In vain were my attempts to make things easier for myself. The output is small, I set about solving the problem. At one time I tried to bungled a solution using .htaccess, I found this.

RewriteCond %(HTTP:Authorization) ^Basic.* RewriteRule (.*) index.php?authorization=%(HTTP:Authorization)

Alas, I had problems with Joomla, everything is fine with it in .htaccess, and here I am with my code. After taking the time to go through various combinations, I agreed that I would not rewrite .htaccess. From the beginning, the code was very raw, so raw that I just copy-pasted it.

Hello($_SERVER["PHP_AUTH_USER"]).

"; echo "

You have entered a password ($_SERVER["PHP_AUTH_PW"]).

"; } ?>

It works great, but my goal was to implement authorization by using existing code words that are located in the table.

get("user") === null) ( $db = JFactory::getDbo(); $query = $db->getQuery(true); $query->select("*"); $query->from ("#__beda_users"); $query->where("codeword = ".$query->quote($_SERVER["PHP_AUTH_PW"])); if($res = $db->setQuery($query)-> loadAssoc()) ( $session->set("user", $res); ) else ( header("WWW-Authenticate: Basic realm="My Realm""); header("HTTP/1.0 401 Unauthorized"); ) ) else ( //We should be here ) ) ?>

It works, the values ​​change, the session is created. Wonderful, but in the case of a cancel click, the values I don't care what you canceled, after refreshing the page, they return to their place. I decided that I should fight this through unset. I added a line.

If($res = $db->setQuery($query)->loadAssoc()) ( $session->set("user", $res); ) else ( unset($_SERVER["PHP_AUTH_USER"], $_SERVER ["PHP_AUTH_PW"]); header("WWW-Authenticate: Basic realm="My Realm""); header("HTTP/1.0 401 Unauthorized"); )

I tried to cope with these variables, but went deeper and deeper into nowhere. Arranged and check of existence of session, but it is vain. In addition, I needed to complete the session, which, according to the members of the forum, is hardly possible. Got to the bottom of the truth of using an address like login : [email protected]/. Great, the variables have changed $_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"]. Alas, this did not become my final decision, because after the end of the session $_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"] continued to perform. Just like after forcing $session->set("user", null). I am beside myself with anger, but I knew that I would not forgive myself if I let it back down. I wrote a separate condition for checking for a logout.

If($_SERVER["PHP_AUTH_USER"]=="logout")( $session->set("user", null); header("Refresh: 0;URL="); ) if(!isset($_SERVER[ "PHP_AUTH_PW"])) ( header("WWW-Authenticate: Basic realm="My Realm""); header("HTTP/1.0 401 Unauthorized"); ) else ( //If a matching codeword is found, create a session if ($session->get("user") === null) ( $db = JFactory::getDbo(); $query = $db->getQuery(true); $query->select("*"); $query->from("#__beda_users"); $query->where("codeword = ".$query->quote($_SERVER["PHP_AUTH_PW"])); if($res = $db->setQuery( $query)->loadAssoc()) ( $session->set("user", $res); ) else ( unset($_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"]); header("WWW -Authenticate: Basic realm="My Realm""); header("HTTP/1.0 401 Unauthorized"); ) ) else ( //We should be here ) )

The condition worked, however, after reloading the page, when a window appears asking you to enter Username and password, it accepted them, and after refreshing the page, it prompted me to enter the password again, I click "Cancel", $_SERVER["PHP_AUTH_USER"] will logout. I'll update the values, it will assign them. And I will click on the cancel from will return the previous values. Trouble.

In the end, the final solution looks like this.

$session = JFactory::getSession(); function http_auth($session)( $session->set("user", null); unset($_SERVER["PHP_AUTH_PW"], $_SERVER["PHP_AUTH_USER"]); header("WWW-Authenticate: Basic realm=" Auth""); header("HTTP/1.0 401 Unauthorized"); ) if($_SERVER["PHP_AUTH_USER"]=="logout")( $session->set("user", null); header("Refresh : 0;URL="); ) if($session->get("user") === null)( if(!isset($_SERVER["PHP_AUTH_PW"]))( http_auth($session); ) else ( $pw = $_SERVER["PHP_AUTH_PW"]; $db = JFactory::getDbo(); $query = $db->getQuery(true); $query->select("*"); $query->from ("#__beda_users"); $query->where("codeword = ".$query->quote($pw)); if($res = $db->setQuery($query)->loadAssoc())( $session->set("user", $res); ) else ( http_auth($session); ) ) ) else ( if(!isset($_SERVER["PHP_AUTH_PW"]))( http_auth($session); ) )

We will learn how to do simple user authentication on the site. The site can have pages only for authorized users and they will fully function if we add our authentication block to them. To create it, you need a MySQL database. It can have 5 columns (minimum) or more if you want to add information about users. Let's name the database "Userauth".

Let's create the following fields in it: ID for counting the number of users, UID for the user's unique identification number, Username for the username, Email for his email address and Password for the password. You can use the user and the database you already have for authorization, only, as in the case of a new database, create the following table in it.

MySQL code

CREATE TABLE `users` (`ID` int (11) NOT NULL AUTO_INCREMENT, `UID` int (11) NOT NULL, `Username` text NOT NULL, `Email` text NOT NULL, `Password` text NOT NULL, PRIMARY KEY (`ID`)) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Now let's create the "sql.php" file. It is responsible for connecting to the database. This code firstly creates variables for the server and the user when it connects to the server. Second, it will select the database, in this case "USERAUTH". This file must be included in "log.php" and "reg.php" to access the database.

PHP code

//Your MySQL username$pass = "redere"; //password $conn = mysql_connect ($server, $user, $pass); //connection to server$db = mysql_select_db("userauth", $conn); //select database if (!$db) ( //if can't select database echo "Sorry, error:(/>"; //Show error message exit(); //Allows other PHP scripts to run } ?>

Next is the login page, let's call it "login.php". First, it checks the entered data for errors. The page has fields for username, password, a submit button, and a registration link. When the user clicks the "Login" button, the form will be processed by the code from the "log.php" file, and then the login will occur.

PHP code

0) { //if there are session errors$err = "

"; //Start a table foreach ($_SESSION["ERRMSG"] as $msg) ( //recognize each error$err .= " "; //write it into a variable) $err .= "
" . $msg . "
"; //closing the table unset($_SESSION["ERRMSG"]); //delete session } ?> Login form
Username
Password
Registration

Then we write a script for logging in. Let's name it "log.php". It has a feature to clean up SQL injection inputs that can mess up your script. Second, it receives the form data and validates it for validity. If the input data is correct, the script sends the user to the authorized users page, if not, it sets errors and sends the user to the login page.

PHP code

//start session for recording function Fix($str) ( //clear fields $str = trim($str); if (get_magic_quotes_gpc()) ( $str = stripslashes ($str); ) //array to store errors$errflag = false ; //error flag $username = Fix($_POST["username"]); //Username$password = Fix($_POST["password"]);//password ) //check password if ($password == "") ( $errmsg = "Password missing"; //error $errflag = true ; //raise flag on error ) //if the error flag is raised, redirects back to the registration form // logs errors session_write_close(); //closing the session //redirect exit(); ) //request to the database$qry = "SELECT * FROM `users` WHERE `Username` = "$username" AND `Password` = "" . md5 ($password) . """; $result = mysql_query($qry); // check if the request was successful (if there is data on it) if (mysql_num_rows ($result) == 1) ( while ($row = mysql_fetch_assoc ($result)) ( $_SESSION["UID"] = $row["UID"]; //get the UID from the database and put it into the session$_SESSION["USERNAME"] = $username; //sets if the username matches the session one session_write_close(); //closing the session header("location: member.php"); //redirect) ) else ( $_SESSION["ERRMSG"] = "Invalid username or password"; //error session_write_close(); //closing the session header("location: login.php"); //redirect exit(); ) ?>

Let's make a registration page, let's call it "register.php". It is similar to the login page, only it has a few more fields, and instead of a registration link, a link to login.php in case the user already has an account.

PHP code

0) { //if there are session errors$err = "

"; //beginning of table foreach ($_SESSION["ERRMSG"] as $msg) ( // sets each error$err .= " "; //writes them into a variable) $err .= "
" . $msg . "
"; // end of table unset ($_SESSION["ERRMSG"]); //destroys the session } ?> Registration form
Username
Email
Password
Password repeat
I have an account

Now let's make a registration script in the "reg.php" file. It will include "sql.php" to connect to the database. The same function is used as in the login script to clear the input field. Variables for possible errors are set. Next is a function to create a unique identifier that has never been provided before. The data is then retrieved from the registration form and validated. It checks that the email address is in the correct format and that the password is re-entered correctly. The script then checks to see if there is a user with the same name in the database, and if so, reports an error. And finally, the code adds the user to the database.

PHP code

//start session for recording function Fix($str) ( //clear fields $str = @trim($str); if (get_magic_quotes_gpc()) ( $str = stripslashes ($str); ) return mysql_real_escape_string($str); ) $errmsg = array(); //array to store errors$errflag = false ; //error flag $UID = "12323543534523453451465685454";//unique ID $username = Fix($_POST["username"]); //Username$email = $_POST["email"]; //Email $password = Fix($_POST["password"]);//password $rpassword = Fix($_POST["rpassword"]);//password repeat //check username if ($username == "") ( $errmsg = "Username missing"; //error $errflag = true ; //raise flag on error) //check Email if(!eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@+(\.+)*(\.(2,3 ))$", $email)) ( //must follow the format: [email protected]$errmsg = "Invalid Email"; //error $errflag = true ; //raise flag on error } //check password if ($password == "") ( $errmsg = "Password missing"; //error $errflag = true ; //raise flag on error } //check password repeat if ($rpassword == "") ( $errmsg = "Repeated password missing";//error $errflag = true ; //raise flag on error } // check if the password is valid if (strcmp($password, $rpassword) != 0) ( $errmsg = "Passwords do not match";//error $errflag = true ; //raise flag on error } //check if the username is free if ($username != "") ( $qry = "SELECT * FROM `users` WHERE `Username` = "$username""; // query MySQL $result = mysql_query ($qry); if ($result) ( if (mysql_num_rows ($result) > 0) ( //if the name is already in use$errmsg = "Username already in use"; //error message$errflag = true; //raise flag on error) mysql_free_result($result); ) ) //if the data is not validated, redirects back to the registration form if ($errflag) ( $_SESSION["ERRMSG"] = $errmsg; //error message session_write_close(); //closing the session header("location: register.php"); //redirect exit(); ) //adding data to the database$qry = "INSERT INTO `userauth`.`users`(`UID`, `Username`, `Email`, `Password`) VALUES("$UID","$username","$email","" . md5 ($password) . "")"; $result = mysql_query($qry); //check if the add request was successful if ($result) ( echo "Thank you for registering, " .$username . ". Please login here"; exit (); ) else ( die ("Error, check back later"); ) ?>

You also need to make a script to log the user out of the system. It terminates the session for the user with the given unique id and name, and then redirects the user to the login page.

PHP code

Finally, the "auth.php" script can be used to make pages accessible only to authorized users. It checks the login data and, if it is correct, allows the user to view the pages, and if not, asks to log in. In addition, if someone tries to hack the site by creating one of the sessions, it will be aborted, as in the general case.

PHP code

One of the conditions in the code above is the subject of the question in .

The following code needs to be inserted on the page for authorized users, it is called, for example, "member.php", and you can call it whatever you like.

PHP code

You are authorized to access this page. Go out ( )

User authentication is ready!

To send an "Authentication Required" message to the client's browser, which in turn will result in a dialog box for entering a username and password. After the client has entered his name and password, the script will be called again, but with the predefined variables PHP_AUTH_USER , PHP_AUTH_PW and AUTH_TYPE , which respectively contain the username, password and authentication type. These variables can be found in the $_SERVER and $HTTP_SERVER_VARS arrays. Currently only "Basic" authentication is supported. You can also read a more detailed description of the function header() .

An example of a script fragment that forces the client to log in to view the page:

HTTP Authentication Example

if (!isset($_SERVER [ "PHP_AUTH_USER" ])) (
header( "WWW-Authenticate: Basic realm="My Realm"");

echo "Text to be sent if
if the user clicked the Cancel button"
;
exit;
) else (
echo
"

Hello($_SERVER["PHP_AUTH_USER"]).

" ;
echo "

You have entered a password ($_SERVER["PHP_AUTH_PW"]).

"
;
}
?>

Compatibility note: Be especially careful when specifying HTTP headers. In order to guarantee maximum compatibility with the largest number of different clients, the word "Basic" must be capitalized "B", the region (realm) must be enclosed in double (not single!) quotes, and exactly one space must precede the code 401 in the title HTTP/1.0 401 .

Instead of just displaying the PHP_AUTH_USER and PHP_AUTH_PW variables on the screen, you may need to check if they are correct. To do this, use a database query or search for a user in a dbm file.

You can observe the features of the Internet Explorer browser. It is very demanding on the parameter of transmitted headers. Header specification WWW-Authenticate before sending HTTP/1.0 status 401 is a little trick.

As of PHP 4.3.0, in order to prevent someone from writing a script that reveals the password to a page that uses external authentication, the PHP_AUTH variables are not set if that page uses external authentication and secure mode is set. Regardless, the REMOTE_USER variable can be used to authenticate an externally authenticated user. So you can always use the $_SERVER["REMOTE_USER"] variable.

Note: PHP uses the AuthType directive to specify whether external authentication is used or not.

It should be noted that all of the above does not prevent passwords for pages requiring authorization from being stolen by anyone who controls pages without authorization located on the same server.

Both Netscape Navigator and Internet Explorer clear the current window's authentication cache for the given realm when received from the server. This can be used to force the user to log out and re-display the username and password dialog box. Some developers use this to time-limit logins or to provide a logout button.

An example of HTTP authentication with forced entry of a new login/password pair

function authenticate()(
header( "WWW-Authenticate: Basic realm="Test Authentication System"");
header("HTTP/1.0 401 Unauthorized");
echo "You must enter a valid username and password to access the resource \n";
exit;
}

If (!isset($_SERVER [ "PHP_AUTH_USER" ]) ||
($_POST [ "SeenBefore" ] == 1 && $_POST [ "OldAuth" ] == $_SERVER [ "PHP_AUTH_USER" ])) (
authenticate ();
}
else(
echo
"

Welcome: ($_SERVER["PHP_AUTH_USER"])
" ;
echo "Previous login: ($_REQUEST["OldAuth"])";
echo "

\n";
echo "\n";
echo "\n";
echo "\n";
echo "

\n" ;
}
?>

This behavior is not defined by the HTTP Basic Authentication standards, so you should not depend on it. As tests have shown, the Lynx browser does not clear the authorization cache when receiving a 401 status from the server, and by clicking "Back" and then "Forward" in sequence, it is possible to open such a page, provided that the required authorization attributes have not changed. However, the user can press the "_" key to clear the authentication cache.

Also note that prior to PHP 4.3.3, HTTP authentication did not work on servers running Microsoft IIS if PHP was installed as a CGI module, due to some IIS restrictions. In order to get it to work correctly in PHP 4.3.3+, you must edit the IIS configuration setting called "Directory Security". Click on the "Edit" label and check the "Anonymous Access" option, all other fields should remain unchecked.

Another limitation if you are using IIS via ISAPI: PHP_AUTH_* variables are not defined, but the HTTP_AUTHORIZATION variable is available. Example code you could use: list($user, $pw) = explode(":", base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6)));

Note regarding IIS: In order for HTTP authentication to work correctly in IIS, the cgi.rfc2616_headers option in the PHP configuration must be set to 0 (the default value).

Attention: In case protected mode is used, the UID of the current script will be added to the realm part of the WWW-Authenticate header.



<<< Назад Content Forward >>>
If you have more questions or something is not clear - welcome to our

It is possible to use the function header() to send a message "Authentication Required" browser, forcing it to show a window for entering a username and password. Once the user has filled in the login and password, the link containing the PHP script will be called again with the predefined variables PHP_AUTH_USER , PHP_AUTH_PW , and AUTH_TYPE set to login, password and authentication type respectively. These predefined variables are stored in the $_SERVER and $HTTP_SERVER_VARS arrays. Both types are supported: "Basic" and "Digest" (as of PHP 5.1.0). See function for details. header().

An example of a script fragment that forces the client to log in to view the page:

Beispiel #6 Basic HTTP authentication example

if (!isset($_SERVER [ "PHP_AUTH_USER" ])) (
header( "WWW-Authenticate: Basic realm="My Realm"");

echo "Text to be sent if
if the user clicked the Cancel button"
;
exit;
) else (
echo
"

Hello ( $_SERVER [ "PHP_AUTH_USER" ]) .

" ;
echo "

You have entered a password( $_SERVER [ "PHP_AUTH_PW" ]) .

" ;
}
?>

Beispiel #7 Digest HTTP authentication example

This is an example implementation of a simple Digest HTTP authentication script. See » RFC 2617 for details.

$realm = "Restricted area" ;

//user => password
$users = array("admin" => "mypass" , "guest" => "guest" );

if (empty($_SERVER [ "PHP_AUTH_DIGEST" ])) (
header("HTTP/1.1 401 Unauthorized");
header( "WWW-Authenticate: Digest realm="". $realms.
"",qop="auth",nonce="" . uniqid(). "",opaque="" . md5 ($realm ). """);

Die( "The text sent if the user clicked Cancel");
}

// parse the PHP_AUTH_DIGEST variable
if (!($data = http_digest_parse ($_SERVER [ "PHP_AUTH_DIGEST" ])) ||
!isset($users [ $data [ "username" ]]))
die( "Wrong data!");

// generate the correct response
$A1 = md5 ($data [ "username" ] . ":" . $realm . ":" . $users [ $data [ "username" ]]);
$A2 = md5($_SERVER [ "REQUEST_METHOD" ]. ":" . $data [ "uri" ]);
$valid_response = md5 ($A1 . ":" . $data [ "nonce" ]. ":" . $data [ "nc" ]. ":" . $data [ "cnonce" ]. ":" . $data [ "qop" ]. ":" . $A2 );

if ($data [ "response" ] != $valid_response )
die( "Wrong data!");

// ok, login and password are correct
echo "You are logged in as: " . $data["username"];

// http auth header parsing function
function http_digest_parse($txt )
{
// protect against missing data
$needed_parts = array("nonce" => 1 , "nc" => 1 , "cnonce" => 1 , "qop" => 1 , "username" => 1 , "uri" => 1 , "response" => 1);
$data = array();
$keys = implode ("|" , array_keys ($needed_parts ));

preg_match_all ("@(" . $keys . ")=(?:([\""])([^\2]+?)\2|([^\s,]+))@", $txt , $matches , PREG_SET_ORDER );

Foreach ($matches as $m ) (
$data [ $m [ 1 ]] = $m [ 3 ] ? $m [ 3 ] : $m [ 4 ];
unset($needed_parts [ $m [ 1 ]]);
}

Return $needed_parts ? false : $data ;
}
?>

Comment: Compatibility note

Be especially careful when specifying HTTP headers. In order to guarantee maximum compatibility with the largest number of different clients, the word "Basic" must be capitalized "B", the region (realm) must be enclosed in double (not single!) quotes, and exactly one space must precede the code 401 in the title HTTP/1.0 401. Authentication parameters must be separated by commas, as shown in the Digest Authentication example above.

Instead of just displaying the PHP_AUTH_USER and PHP_AUTH_PW variables on the screen, you may need to check if they are correct. To do this, use a database query or search for a user in a dbm file.

You can observe the features of the Internet Explorer browser. It is very demanding on the parameter of transmitted headers. Header Trick WWW-Authenticate before sending status HTTP/1.0 401 so far it works for him.

As of PHP 4.3.0, in order to prevent someone from writing a script that reveals the password to a page that uses external authentication, the PHP_AUTH variables are not set if that page uses external authentication and secure mode is set. Regardless, the REMOTE_USER variable can be used to authenticate an externally authenticated user. So you can always use the $_SERVER["REMOTE_USER"] variable.

Comment: Configuration note

PHP uses directive indication AuthType to indicate whether external authentication is used or not.

It should be noted that all of the above does not prevent passwords for pages that require authorization from being stolen by anyone who controls pages without authorization located on the same server.

Both Netscape Navigator and Internet Explorer clear the current window's authentication cache for the given realm when received from the server. This can be used to force the user to log out and re-display the username and password dialog box. Some developers use this to time-limit logins or to provide a logout button.

Beispiel #8 HTTP Authentication example forcing a new login/password pair

function authenticate()(
header( "WWW-Authenticate: Basic realm="Test Authentication System"");
header("HTTP/1.0 401 Unauthorized");
echo "You must enter a valid username and password to access the resource \n";
exit;
}

if (!isset($_SERVER [ "PHP_AUTH_USER" ]) ||
($_POST [ "SeenBefore" ] == 1 && $_POST [ "OldAuth" ] == $_SERVER [ "PHP_AUTH_USER" ])) (
authenticate();
) else (
echo "

Welcome: ". htmlspecialchars ($_SERVER [ "PHP_AUTH_USER" ]) . "
" ;
echo "Previous login: ". htmlspecialchars($_REQUEST["OldAuth"]);
echo "

\n";
echo "\n";
echo ". htmlspecialchars ($_SERVER [ "PHP_AUTH_USER" ]) . "\" />\n" ;
echo "\n";
echo "

\n" ;
}
?>

This behavior is not regulated by the standards HTTP Basic-authentication, therefore, you should not depend on it. Browser testing Lynx showed that Lynx does not clear the authorization cache when receiving a 401 status from the server, and by clicking "Back" and then "Forward" in sequence, it is possible to open such a page, provided that the required authorization attributes have not changed. However, the user can press the key "_" to clear the authentication cache.

Also note that prior to PHP 4.3.3, HTTP authentication did not work on servers running Microsoft IIS if PHP was installed as a CGI module, due to some IIS restrictions. In order to get it to work correctly in PHP 4.3.3+, you must edit the IIS configuration setting called " Directory Security". Click on the inscription " Edit" and set the option " Anonymous Access", all other fields should be left unchecked.

Another limitation if you are using IIS via ISAPI and PHP 4: Variables PHP_AUTH_* are not defined, but at the same time the variable is available HTTP_AUTHORIZATION. Sample code you could use: list($user, $pw) = explode(":", base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6)));

Comment: Note regarding IIS:
In order for HTTP authentication to work correctly in IIS, the cgi.rfc2616_headers option in the PHP configuration must be set to 0 (default value).

Comment:

In case safe mode is used, the UID of the current script will be added to realms-header part WWW-Authenticate.

Internet