- <VirtualHost *:80>
- DocumentRoot /home/phpguides/
- ServerName http://www.phpguides.eu
- ServerAlias phpguides.eu
- CustomLog /var/log/httpd_access_logs/www.phpguides.eu-access_log combined
- php_flag magic_quotes_gpc off
- </VirtualHost>
Dictionary wrote:* hash = something that makes the "hashed" string unreadable to the naked eye, for example with sha1() or md5()
* salt = random word inserted into the password to make it brute-force safe
There is plenty of ways of doing a good login-system, what you should take in mind before you start though is where your site is located.
Case 1
If you rent space from some webhosting company you will probably be sharing the same server with hundreds of other costumers. What you should check for here is if you can read files outside your directory, thats done with a simple filebrowser script (which you also can read about in one of our guides). If you can you should probably think about changing hostingcompany, because if you run a popular site and someone in boredom decides to dns your domain and gets the bright idea "hey, that site is on the same ip as my site". He can simply access your whole source code and therefor get your mysql username/password or whatever else information you got there like accesslevels and so on.
Case 2
You have your own server or that you cant browser outside your own directory with scripts on a hosting company server. This is good, this way no one will get hold of your source, except the serveradmin ofc.
One more thing..
Before we jump into the fun there is one more thing you should take in consideration and that is the php flag "magic_quotes_gpc", this should normaly be switched to off, but in some cases hosts do have it switched on. What this built in php function does is adding slashes to _POST, _GET and _COOKIE, whichs means if you have it activated and use a form with a input where you type "Hi, my name is Daniel, what's yours?" and press the submit button your text will then get converted to "Hi, my name is Daniel, what\'s yours?" which is completly nuts because when you then escape it before you insert it to a mysql query it will be double escaped. Lets say you were not gonna use it for a mysql query in the last example, you then would have had an escaped string which you would have had to run stripslashes() on to view it normally, how smart is that?
How do I see if its activated then?
paste this in a file named nfo.php
- <?
- phpinfo();
- ?>
then just visit the nfo.php file and search for magic_quotes_gpc and you´ll see if its activated or not.
Well, if its activated, how do i turn it off?
There is two ways of doing this, if your host allows you to use .htaccess files you can just type "php_flag magic_quotes_gpc off" (without the "" ofcourse)
Another way is to mail your hostprovider and tell them to add this line to your apache vhost, that should look something like this:
So, now when we have passed all that, shall we start?
In this guide i´ll put everything in different functions, named checklogin(), login(), logout(), checklevel(currentlevel), and they are all put in functions.php which is included in index.php. If you dont understand the layout in the end you should probably check out my guide under site layouts, this way all newcomers to php will probably understand what we are doing here because its simple and straight forward instead of making cracy classes and so on.
___________________________________________________________________________________________________________________________________________________________________________________
Ill start with giving you a MySQL table for users and accesslevels we will be working with, in this guide i got no max limits on either username or password, if you want that feel free to add that yourself.
- CREATE TABLE `users` (
- `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
- `username` TEXT NOT NULL ,
- `password` TEXT NOT NULL ,
- `mail` TEXT NOT NULL ,
- `firstname` TEXT NOT NULL ,
- `lastname` TEXT NOT NULL ,
- `ip` TEXT NOT NULL ,
- `banned` LONGTEXT NOT NULL ,
- `activated` ENUM( '0', '1' ) NOT NULL DEFAULT '0',
- `lastseen` INT NOT NULL ,
- `regdate` INT NOT NULL ,
- `activate_code` VARCHAR( 10 ) NOT NULL
- ) ENGINE = INNODB;
- CREATE TABLE `accesslevels` (
- `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
- `uid` BIGINT(20) NOT NULL,
- `levels` TEXT NOT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=INNODB;
Pretty basic infos gathered here, nothing else.
___________________________________________________________________________________________________________________________________________________________________________________
Ill start with showing you the basic site layout before we start adding the login:
index.php
- <?
- include("config.php");
- include(incurl."functions.php");
- include("Mail.php");
- if(!preg_match('/^[a-z0-9\+\/\=\#\%\_\^\@\.\?\&\-]+$/i', $_SERVER["QUERY_STRING"]) && strlen($_SERVER["QUERY_STRING"]) != 0) {
- echo "Invalid string"; exit;
- }
- $connect = @mysql_connect(myhost,myuser,mypwd);
- mysql_select_db(mydb) or die(mysql_error());
- session_start();
- ?>
- <? echo "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"; ?>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
- <title>Testsite</title>
- <link rel="stylesheet" type="text/css" href="main.css" />
- </head>
- <body>
- </body>
- </html>
- <?
- mysql_close($connect);
- ?>
config.php
- <?
- # mysql info
- define("myhost", "localhost");
- define("myuser", "testsite");
- define("mypwd", "asdasd");
- define("mydb", "testsitedb");
- # Site urls
- define("url", "http://www.testsite.eu/"); //Adress to the site
- define("incurl", "/home/testsite/"); // the folder where all files is except index.php
- # Mail info
- $mejl_ip = "mail.testsite.eu";
- $mejl_port = 25;
- $mejl_auth = false;
- $mejl_username = "";
- $mejl_password = "";
- ?>
functions.php
- <?
- function getresult($query) {
- $grek = mysql_fetch_array(mysql_query($query));
- return $grek;
- }
- function mejla($to, $title, $by, $body) {
- global $mejl_ip, $mejl_port, $mejl_auth, $mejl_username, $mejl_password;
- $recipients = $to;
- $headers["Date"] = date("D").", ".date("d")." ".date("M")." ".date("Y")." ".date("H").":".date("i").":".date("s")." ".date("O");
- $headers["From"] = "PHPguides <".$by.">";
- $headers["To"] = $to;
- $headers["Subject"] = $title;
- $headers["Content-Type"] = "text/plain; charset=\"iso-8859-1\"";
- $params["host"] = $mejl_ip;
- $params["port"] = $mejl_port;
- $params["auth"] = $mejl_auth;
- $params["username"] = $mejl_username;
- $params["password"] = $mejl_password;
- $mail_object =& Mail::factory("smtp", $params);
- $mail_object->send($recipients, $headers, $body);
- }
- ?>
___________________________________________________________________________________________________________________________________________________________________________________
Lets have a look on the login function
- function login() {
- global $connect;
- //convert username to something secure so we can user it in a db query
- $username = md5(strtolower($_POST["username"]));
- //converting the password that the user typed to whatever hash you made on the registration process
- $password = sha1("thiswould".md5($_POST["password"])."bepretty");
- $saveme = $_POST["saveme"];
- $sql = mysql_query("select id, username, password, mail, activated, banned from users where md5(lower(username))='".$username."'");
- if(mysql_num_rows($sql) == 1) {
- while($RS = mysql_fetch_array($sql)) {
- $real_pwd = $RS["password"];
- $real_username = $RS["username"];
- $real_mail = $RS["mail"];
- $real_banned = $RS["banned"];
- $real_activated = $RS["activated"];
- $real_uid = $RS["id"];
- }
- if($real_pwd == $password) {
- if(strlen($real_banned) == 0) {
- if($real_activated == 1) {
- if(mysql_result(mysql_query("select count(*) from accesslevels where uid='".$real_uid."'"),0) == 1) {
- $level = mysql_result(mysql_query("select levels from accesslevels where uid='".$real_uid."'"),0);
- }else{
- $level = "";
- }
- $_SESSION["lvl"] = $level;
- $_SESSION["uid"] = $real_uid;
- $_SESSION["pwd"] = $real_pwd;
- if($saveme == 1) {
- $expire = pow(2, 31) -1;
- setcookie("saveme", sha1(strtoupper($real_mail)."your".$real_username."kindof".$real_pwd."lol"), $expire);
- setcookie("uid", $real_uid, $expire);
- }
- if($saveme == 1) {
- mysql_query("update users set lastseen=UNIX_TIMESTAMP(), ip='".$_SERVER["REMOTE_ADDR"]."' where id='".$real_uid."'");
- }else{
- mysql_query("update users set lastseen=UNIX_TIMESTAMP() where id='".$real_uid."'");
- }
- mysql_close($connect);
- header("Location: ".url);
- }else{
- echo "Your account isnt activated yet, please check your mail.";
- }
- }else{
- echo "Your banned due to: ".$real_banned;
- }
- }else{
- echo "Password missmatch";
- }
- }else{
- echo "There was no such username";
- }
- exit;
- }
So now you might wonder if im nuts or not, why is the dude using both cookies and sessions?
There is one good reason for this, when you are using a shared server all session data is saved on the same place (if you dont specify otherwise) therefor othersites can access your sessiondata and therefor use your login. In this case they cant because they need the cookie too and thats stored on your computer which they dont have access to. The cookie is also used for users that has checked the "Remember me" checkbox so they dont have to login each time they visit the site. You will probably understand more when you check out the checklogin function.
___________________________________________________________________________________________________________________________________________________________________________________
- function checklogin() {
- global $connect;
- if(isset($_COOKIE["saveme"]) && !isset($_SESSION["uid"]) && strlen($_COOKIE["saveme"]) > 0 && is_numeric($_COOKIE["uid"])) {
- $checksave = getresult("select id, mail, username, password from users where md5(id)='".md5($_COOKIE["uid"])."' and ip='".$_SERVER["REMOTE_ADDR"]."'");
- if(sha1(strtoupper($checksave["mail"])."your".$checksave["username"]."kindof".$checksave["password"]."lol") == $_COOKIE["saveme"]) {
- if(mysql_result(mysql_query("select count(*) from accesslevels where uid='".$checksave["id"]."'"),0) == 1) {
- $level = mysql_result(mysql_query("select levels from accesslevels where uid='".$checksave["id"]."'"),0);
- }else{
- $level = "";
- }
- $_SESSION["lvl"] = $level;
- $_SESSION["uid"] = $checksave["id"];
- $_SESSION["pwd"] = $checksave["password"];
- }else{
- mysql_close($connect);
- header("Location: ".url."?login=logout&reason=cookiechange");
- }
- }
- if ( isset($_SESSION["uid"]) || isset($_SESSION["lvl"]) || isset($_SESSION["pwd"]) ) {
- $id = $_SESSION["uid"];
- if (!preg_match('/^[0-9]+$/i', $id)) {
- mysql_close($connect);
- header("Location: ".url."?login=logout&reason=cookiechange");
- exit;
- }
- $grek = getresult("select username, banned, password, mail, activated from users where id='".mysql_real_escape_string($id)."'");
- $asd = getresult("select levels from accesslevels where uid='".mysql_real_escape_string($id)."'");
- if ($_SESSION["pwd"] != $grek["password"] || $_SESSION["lvl"] != $asd["levels"] || strlen($grek["banned"]) > 0 || $grek["activated"] == 0) {
- mysql_close($connect);
- header("Location: ".url."?login=logout&reason=cookiechange");
- exit;
- }
- if(isset($_COOKIE["saveme"]) && sha1(strtoupper($grek["mail"])."your".$grek["username"]."kindof".$grek["password"]."lol") != $_COOKIE["saveme"]) {
- mysql_close($connect);
- header("Location: ".url."?login=logout&reason=cookiechange");
- exit;
- }
- }
- }
That wasnt so complicated, or was it?
Why am i only using ip-check for auto-login/remember me logins? This is because some users do have a load-balanced network using different ips each time they refresh a page, therefor they now have a chance of being logged in without the remember me/auto-login feature.
___________________________________________________________________________________________________________________________________________________________________________________
Now to the even easier part, the logout function.
- function logout() {
- global $connect;
- $userid = $_SESSION["uid"];
- session_unset();
- session_destroy();
- if(isset($_COOKIE["saveme"])) {
- setcookie("uid", "", time());
- setcookie("saveme", "", time());
- }
- $user = getusername($userid);
- if(!isset($_GET["reason"])) {
- //make some kind of log
- mysql_query("insert into log (section, date, line) Values(\"login\",UNIX_TIMESTAMP(), \"$user logged out from ".$_SERVER["REMOTE_ADDR"]." (".gethostbyaddr($_SERVER["REMOTE_ADDR"]).")\")");
- }elseif($_GET["reason"] == "cookiechange") {
- mysql_query("insert into log (section, date, line) Values(\"login\",UNIX_TIMESTAMP(), \"$user logged out from ".$_SERVER["REMOTE_ADDR"]." (".gethostbyaddr($_SERVER["REMOTE_ADDR"]).") due to changes in his cookie.\")");
- }
- mysql_close($connect);
- header("Location: ".url);
- }
___________________________________________________________________________________________________________________________________________________________________________________
Only the accesslevels left now, if you havent understood a thing of this guide, try typing the code yourself and maybe change some stuff to get a feeling for it. But my guess is that most of you are pretty familiar with all this stuff already.
The accesslevel string looks something like this, ".ADM_USERS, ADM_NEWS, ADM_COVERAGE, ADM_ARTICLES, ADM_COMMENTS", to make this string ill give you a simple example with checkboxes:
- <?
- if($_GET["update"] == "levels" && is_numeric($_GET["userid"]) && checklevel("ADM_USERS")) {
- $levels = ".".implode(", ", $_POST["levels"];
- //now just insert $levels into the accesslevels table
- if(mysql_result(mysql_query("select count(*) from accesslevels where uid='".$_GET["userid"]."'"),0) == 1) {
- mysql_query("update accesslevels set levels='".mysql_real_escape_string($levels)."' where uid='".$_GET["userid"]."'");
- }else{
- mysql_query("insert into accesslevels (uid, levels) Values('".$_GET["userid"]."', '".mysql_real_escape_string($levels)."')");
- }
- ?>
- <meta http-equiv="Refresh" content="0; URL=<?=url?>whereeveryouwanttobetransfered" />
- <?
- }else{
- ?>
- <form method="post" action="<?=url?>?p=admin&s=users&userid=<?=$_GET["userid"]?>&update=levels">
- <input type="checkbox" name="levels" value="ADM_USERS" /><br />
- <input type="checkbox" name="levels[]" value="ADM_NEWS" /><br />
- <input type="checkbox" name="levels[]" value="ADM_COMMENTS" /><br />
- <input type="submit" value="Update" />
- </form>
- <?
- }
- ?>
___________________________________________________________________________________________________________________________________________________________________________________
That about sums it up, now our index.php looks something like this when we have implemented these functions:
- include("config.php");
- include(incurl."functions.php");
- include("Mail.php");
- if(!preg_match('/^[a-z0-9\+\/\=\#\%\_\^\@\.\?\&\-]+$/i', $_SERVER["QUERY_STRING"]) && strlen($_SERVER["QUERY_STRING"]) != 0) {
- echo "Invalid string"; exit;
- }
- $connect = @mysql_connect(myhost,myuser,mypwd);
- mysql_select_db(mydb) or die(mysql_error());
- session_start();
- if($_GET["login"] == "logout") {
- logout();
- }
- if(isset($_POST["username"]) && isset($_POST["password"])) {
- login();
- }
- checklogin();
- ?>
- <? echo "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"; ?>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
- <title>Testsite</title>
- <link rel="stylesheet" type="text/css" href="main.css" />
- </head>
- <body>
- <?
- if(isset($_SESSION["uid"])) {
- ?>
- Congrants your logged in.<br />
- <a href="<?=url?>?login=logout">Log out</a><br />
- <?
- if(checklevel("ADM_USERS")) {
- echo "<a href=\"".url."?p=admin&s=users\">User admin</a>";
- }
- }else{
- ?>
- <form method="post" action="<?=url?>">
- <input type="text" name="username" value="Username" /><br />
- <input type="password" name="password" /><br />
- <input type="checkbox" name="saveme" value="1" /><br />
- <input type="submit" value="Login" /><br />
- </form>
- <?
- }
- ?>
- </body>
- </html>
- <?
- mysql_close($connect);
- ?>
