Creating a PHP CMS – Part 7

This post is part of a series about creating a PHP CMS. Click here to start from the beginning.

Today we will be creating a very simple login system used to restrict access to the administration panel. Site security is such a large topic, I could write an entire series on security alone. We will be creating a login form to check if a user exists, and if the user does exist, it logs them in. We will not be creating a registration form, or a forgotten password form. I may cover these topics in another blog post.

If you would just like to download the finished source code, you can Click here to get it.

Creating a User

The first thing we will be doing is creating a user. To create a user, we first need a database table for storing users. Run the following SQL on your database.

CREATE TABLE users (
    id INTEGER AUTO_INCREMENT,
    username VARCHAR(30),
    password VARCHAR(41),
    PRIMARY KEY (id)
);

This creates a table for storing users, with an ID, username and password. The username can be up to 30 characters long. The password needs to be quite long, because we will be encrypting our passwords. If you are storing passwords, they should always be encrypted, instead of being stored in plain text. This is because if someone manages to get access to the database, they can see all of the passwords if they are stored in plain text.

We will be encrypting our passwords using md5 for now, but after I wrote the login system, I read that sha1 is more secure. I will be using md5 in this tutorial, but easily apply the same ideas to sha1 encryption.

Now we can create a user, using some SQL. I will call the user 'admin', and the password will be 'password'. Using this md5 Hash Generator, the md5 version of the word 'password' is

5f4dcc3b5aa765d61d8327deb882cf99

Now we will create a MySQL query to insert the user into the database.

INSERT INTO users (username, password) VALUES (
    'admin',
    'password'
);

Be sure to replace 'password' with the md5 hash of your password.

The Login Form

Create a new file in the admin directory called login.php. Paste in the following code.

<form method="post" action="">
    <p>
        <label for="name">Username: </label>
        <input type="text" name="username" />
    </p>
 
    <p>
        <label for="password">Password: </label>
        <input type="password" name="password" />
    </p>
 
    <p>
        <input type="submit" id="submit" value="Login" name="submit" />
    </p>
</form>

This is just a simple login form. The 'password' field is using the type of password so that any input is hidden. You can also see that the action attribute of the form is empty. This is because we will be validating the user on the same page.

The Login Functions

In functions.php, we will be creating a few new functions. The first one we will create is called verifyUser. In between the parentheses, add two variables, called $name and $pass. These are the arguments we will accept when calling this function. Inside the function, insert the following code.

// Escape strings
$username = mysql_real_escape_string($name);
$password = mysql_real_escape_string($pass);
 
$result = mysql_query("SELECT * FROM users WHERE username='$username' AND password='$password' LIMIT 1");
 
if (mysql_fetch_array($result)) {
    return true;
} else {
    return false;
}

First, we escape the strings. This is important because any user can get to the login form, not just administrators. Next, we select the row from the 'users' table where both the username and password given are the same as those in the database. LIMIT 1 makes sure that only one row is returned.

Next, we have an if statement. The condition is mysql_fetch_array($result). This means that if there were rows to create an array, the condition is true. If no rows were returned, it returns false. Inside the conditional block, we have return true. This means that when we call this function, if the user exists, we get 'true'. If not, we get 'false'.

The next function we will create is called validateUser. Add the variables $name and $pass in between the parentheses again. Inside the function, enter this code.

$check = verifyUser($name, md5($pass));
 
if ($check) {
    $_SESSION['status'] = 'authorized';
 
    header('location: index.php');
} else {
    return 'Please enter a correct username and password';
}

This stores the result of verifyUser in the $check variable. The arguments used are the username and an md5 hashed version of the password. For example, if the user is verified correctly, $check will have a value of 'true'. Next we have an if statement. The condition is $check, so it will continue if verifyUser returned 'true'.

We have a new variable called $_SESSIONS. This is an array used for storing session information. We are using a session called 'status', and giving it a value of 'authorized'.

Next, we call the header function. The argument is 'location: index.php'. This redirects the user to the file 'index.php' when the user has been verified.

In our else block, we simply return a helpful error message.

For the next step, go to login.php and add a PHP block at the top of the file. First, include functions.php and call the connect function. After connecting to the database, call a function called session_start(). This initializes our session data.

Next, add in this code.

if ($_POST['username'] && $_POST['password']) {
    $result = validateUser($_POST['username'], $_POST['password']);
}

This checks if the user has submitted the form by checking if $_POST['username'] and $_POST['password'] are true. Next we call the validateUser function.

Now go to login.php in your browser and enter the login information that you set. If everything went fine, you should be redirected to index.php. Now try entering incorrect login credentials. You should be back at login.php.

Now we need a function to log the user out. In functions.php, create a new function called logout. Paste in this code inside the function.

if (isset($_SESSION['status'])) {
    unset($_SESSION['status']);
 
    // Remove the cookie
    if (isset($_COOKIE[session_name()])) {
        setcookie(session_name(), '', time() - 1000);
        session_destroy();
    }
}

if (isset($_SESSION['status'])) checks if the user is logged in. If they are, then unset the $_SESSION['status'] variable. Next, we check if a cookie exists with the name of the current session. If the cookie exists, we remove it using setcookie. To remove a cookie, we set an expiration date before the current time. In this case, we set it to 1000 seconds earlier. Next, we use session_destroy(), which destroys our session data.

Now add the following code in login.php, inside the PHP block.

if ($_GET['status'] == 'logout') {
    logout();
}

This checks if the URL contains 'status=logout', and if it is, it calls the logout function. In index.php, add a link to 'login.php?status=logout'. Now try logging out and logging back in to make sure everything works.

However, we haven't restricted access to index.php. We will create a new function called checkMember to restrict access. This function will be very simple.

function checkMember() {
    session_start();
 
    if($_SESSION['status'] != 'authorized') {
        header('location: login.php');
    }
}

First we initialize the session data with session_start. Then, we check if $_SESSION['status'] is not equal to 'authorized', like we set in the validateUser function. This means that the user is not logged in. If they are not logged in, we redirect them to login.php.

In all of the files in the admin directory, except for login.php, call the checkMember function after connecting to the database. Now make sure you are logged out, and try accessing the pages in the admin directory without logging in. If everything went well, you should be redirected to login.php.

That's it for this series. If you've been following the series, you've learned how to connect to a database with PHP, create, read, update and delete pages, and how to secure pages of a site. I hope to write some blog posts expanding on these topics in the future.

Click here to download the finished source code. I've added a few more things, such as a few messages, and a method of checking if a page exists. You can check out the source code to see how I did that.

One thing that I did not discuss is styling. I've recently started trying out the CodeIgniter framework, and I am working on a simple themeing system for it in my spare time.

As always, if you have any questions about today's post, be sure to leave a comment!

Stay Updated

Did you enjoy this post? Don't miss a single post by getting free updates!

60 Comments

  1. August 25, 2009

    It's better header('location: http://fwebde.com/index.php'); than header('index.php');

  2. August 25, 2009

    I have submit a reply, but the Aksimet had blocked it. Please check, thanks!

  3. August 25, 2009

    Hua Chen: I used a relative path because it is redirecting to index.php in the admin directory. I think that a relative path helps to make it more portable across multiple sites.

    If you want to use a full URL, you could use header('location: ' . BASE_URL . '/admin/index.php');

  4. deluxe
    September 9, 2009

    Certainly not working after running discarded error:
    Warning: mysql_connect () [function.mysql-connect]: Access denied for user 'xxx' @ 'xxx' (using password: YES) in / home / free / xx.cz / p / xxxx / root / www / m / functions.php on line 8
    Could not connect. Access denied for user 'xxxx' @ 'xxxx' (using password: YES)
    .... And what should I start? ... Instructions should be cool:) Thanks

  5. September 9, 2009

    deluxe: It looks like a problem with your database connection, mainly with your MySQL user details. Make sure the username and password are correct, and also make sure your user has the correct priviliges for your database.

  6. deluxe
    September 9, 2009

    Everything I did as I was, I do not know where it can be a mistake, I checked the password, the host name and it is properly completed and everything is ok .... must have been some mistake because I tried to hosting the second and The same error .. you'll know whether only works for me is wrong? Thank you for the answer

  7. September 9, 2009

    deluxe: Try creating a new user and using that to connect to the database. Other than that, I don't know what else you could do.

  8. September 14, 2009

    Is there a reason why you chose to digest the passwords in the php code, rather than using the encryption functions in the mysql queries?

  9. September 14, 2009

    @Karl Agius: I'm not sure. The PHP method is just what I learned to do. I guess it would work using MySQL, too.

    Maybe you could use both, and get some sort of 'double encryption' thing going on for extra security.

  10. September 14, 2009

    Double encryption is more overhead and trouble than it's worth, in my opinion, better stick one way or the other. Both work fine, was just curious if there was any benefit to using one over the other. Your way would be less dependent on the db, for example.

  11. September 15, 2009

    @Karl Agius: Interesting. Maybe sometime I (or someone) should test those and see which method takes more time, more CPU usage, memory, etc.

  12. bigbanne2004
    October 6, 2009

    HI
    I couldn’t manage to login, I receive this error “Please enter a correct username and password”. I change the user and password but nothing. Can you help me?

  13. October 6, 2009

    @bigbanne2004: Make sure that you are inserting the MD5 hash of your password into the database. Delete the record from the database, and try again.

  14. bigbanne2004
    October 7, 2009

    Thank you, I change in database the word password with md5 version of this word

  15. January 9, 2010

    Good work!

    One thing to consider: even for beginners, you want to keep security in mind. I'd strongly recommend using prepared SQL statements to avoid potential risks (something like PDO [http://php.net/pdo] or MySQLi [http://php.net/mysqli]).

    Also, consider using htmlentities($user_input, ENT_QUOTES); to guard yourself against mischievous users.

    I know it's complex and might seem out of the scope of a beginner-level article, but good habits are really important to form early.

    Thanks for taking the time to put this together!

    • January 9, 2010

      Thanks for your comments. Those are some good things to remember. Security is a very important consideration.

  16. Amber
    January 14, 2010

    Hi,
    Just wanted to say thanks for the tutorial, I have been looking for a simple tutorial for a while now and being totally new to PHP i thought this was very helpful.

    Thanks :)

  17. Chris
    January 15, 2010

    Nice tutorial i have learned a lot. But i have a small problem. When i type the username and password and then login it redirects me back to the login form. Please note that i have changed the "password" with the md5 hash of this passworrd but the problem still exists. Any solutions ?

    • January 15, 2010

      Interesting problem. When you're redirected back to the login form, does it say "Please enter a correct username and password", or is it just the same as before?

      • Chris
        January 17, 2010

        the same as before. I will try to find the solutions and will post it here :)

        • January 17, 2010

          Hmm. Check out your validateUser() function, and how it's used in login.php and make sure everything is correct.

          Also, after you try logging in, try taking your browser directly to admin/index.php, as it could be a problem with redirecting.

          Also after attempting to login, check your browser's cookies, to see if the cookies are at least created.

  18. Daniel
    January 27, 2010

    Nice job. But take a look at this tutorial is written in German, but check it out.

    http://blog.stevieswebsite.de/eigenes-cms-erstellen/

    • January 27, 2010

      My knowledge of German is quite limited, but that does look like another useful, comprehensive tutorial.

  19. May 12, 2010

    I am having some trouble with creating a page, and, on my localhost version, I get tons of "Warning: mysql_real_escape_string() [function.mysql-real-escape-string]: Access denied for user 'SYSTEM'@'localhost' (using password: NO) in C:\wamp\www\cms\admin\create.php on line 14" for creating pages. I really only get 4, but still.

    Help, and also, I emailed you before about WordPress.

    • May 12, 2010

      Looks like there's a problem validating your MySQL credentials. Make sure that you have spelled the username correctly, and that you have given that user access to the database. Also, it looks like you haven't included a password. If your MySQL user is password-protected, make sure to include that as well.

      • May 12, 2010

        Gagh, I'll look it over.

        Thanks.

      • May 14, 2010

        Hmm, still no fix.

        I'm running on a localhost wamp2.0 setup.

        I doubt that's the problem, as I get similar problems when I put it live on my own site.

      • nick
        May 16, 2010

        you need to move the connect() function higher in the code. it needs to be before the mysql-real-escape-string function call. i put mine after the check member() call. you must be connected to a DB before you can call mysql-real-escape-string.

        • May 16, 2010

          I did not think of that. That could very well be the proper solution to that problem!

        • May 16, 2010

          Thankee, nick!

          Now I just have some errors on my /cms/ index page, saying: Notice: Undefined index: id in C:\wamp\www\cms\index.php on line 22.

          Any help there?

        • May 16, 2010

          LC: Basically that means that you are trying to access something called $array['id'], which does not exist.

          Does line 22 contain something like if ($_GET['id'])? If so, you may want to try changing that to if (isset($_GET['id']))

  20. me
    June 10, 2010

    downloaded the source code to try it out before going on and reading it

    i'm getting this:

    *

    Notice: Undefined index: id in /opt/lampp/htdocs/server/cms/index.php on line 22

    Notice: Undefined index: id in /opt/lampp/htdocs/server/cms/functions.php on line 31

    Notice: Undefined index: id in /opt/lampp/htdocs/server/cms/functions.php on line 75
    Jan 01, 1970

    Notice: Undefined index: id in /opt/lampp/htdocs/server/cms/functions.php on line 53

    • June 11, 2010

      Well, "undefined index" means that you are trying to access part of an array that doesn't exist. For example:

      $foo = array('one'=>1, 'two'=>2);
      echo $foo['three'];

      In this case, $foo['three'] is causing the notice, because the index 'three' has not yet been defined.

      So try looking in the files mentioned by the message, and make sure that the array is being completely created.

  21. August 27, 2010

    I have followed the tutorial exactly, but I can not log in.

    Has anyone else had this problem?

    • August 27, 2010

      Just so I can get this out of the way, just to make sure: Have you inserted your password into the database, or the md5 hash of your password? Because that makes a huge difference.

      • August 27, 2010

        hI

        Yeah the md5 hash was the problem. Sorted now thanks

  22. Jeff
    October 1, 2010

    Great Post! How would you change the order of the pages to be reversed? Newest first ..

    • October 1, 2010

      I assume you're asking about the navigation menu.

      Simply change the SQL query used to fetch the list of posts for the menu from SELECT id, title FROM pages to SELECT id, title FROM pages ORDER BY id DESC.

      This basically reverses the order in which the rows are fetched, as they are normally done in ascending order.

      Hope that helped!

  23. Lee
    October 5, 2010

    Awesome tutorial, have tried quite a few, none of which work, this is by far the best! Have also incorporated TinyMCE into my version. Thanks :-)

  24. Joost
    October 22, 2010

    Sir i want to thank you for this great tutorial. It was to very good use to me.

  25. October 25, 2010

    Hi, I've downloaded your final source code and installed it, but I get the following error:

    Fatal error: Call to undefined function checkmember() in /home/thecatho/public_html/cms/admin/create.php on line 7

    I checked the functions file and it's there but I'm not sure if this is a php compatibility issue or not. Any ideas?

    • October 25, 2010

      Check the source file of admin/create.php. If I remember correctly, the file should include functions.php. Make sure that that file exists and that it contains the function checkmember().

      • October 27, 2010

        yep, it's there and still get the error message.

  26. Ricardo
    November 9, 2010

    Hello,

    Thnx for this good tutorial but i have one question

    how can i change the content of /admin/update.php?id=1 or a other id because you can't go back to the admin page

  27. Mark
    January 11, 2011

    Just wanted to say thanks for this tutorial, it wasn't exactly what I was looking for (Was just after a small blog.include) but after following it, I have an idea now of how CMS work and it's given me ideas for how to make a simple blog include that I didn't have before.

    Are there any chances of you doing a security article? I see mentions of things like htmlentities but don't know where I should be using them so an article as useful as this has been would be excellent.

  28. Mark
    January 11, 2011

    Just out of curiosity, would it be better to include CheckUser() within the major functions that have access to your DB? That way even if someone messed with the webpages/HTML/created their own interface, the functions just wouldn't load for them?

    I don't know if that would work in practice though.

  29. January 27, 2011

    All my dates are 1st Jan 1970.

    I guess that my current date function is not working for whatever reason. Any suggestion as to why this might be happening?

    • January 27, 2011

      Well, since it's using a Unix timestamp, January 1st, 1970 is the same thing as 0. On your site the dates appear to be corrected, so I assume that you solved your problem?

  30. imran
    April 11, 2011

    good tutorial for beginners.......keep it up dear...........

  31. Sameer Chawla
    May 25, 2011

    Hi Eric,

    would you be able to export the DB from PHPmy admin? I was a bit thrown off in the first part.

  32. civilrebel
    July 14, 2011

    Hi, I downloaded tour files and made everything work with my database. Everything works, I can login, update my pages, etc. However, creating a new page causes a connection error:

    Warning: mysql_real_escape_string() [function.mysql-real-escape-string]: A link to the server could not be established

    Now, on the new.php, you have a submit button and are calling the functions.php, however, no php function with an INSERT INTO statement is defined to add an additional row to our SQL table, within the files new.php and functions.php. Is it possible that this is why it does not work? I just got everything from your link. Overall, great tutorial. Tks for this. I'd appreciate your reply.

  33. July 31, 2011

    Great article! One thing I would recommend is to wrap all of the functions into a class or divided classes (DB, User, etc.). This makes the code more portable and more clear. But, great article.

  34. wong
    August 9, 2011

    well, thank a lot for this tutorials it's very good lessons and really useful for me, tx

  35. kgdd
    October 16, 2011

    I have a question. I have followed your tutorial and it works perfectly. I am trying to figure out though how to mask or remove the index.php?id=__ from the url and instead replace it with the title from the MySQL table or the page title. Example: go from: http://www.mysite.com/index.php?id=1 to http://www.mysite.com/about Where about is the title of the page stored either in the webpage or in the MySQL table. Any thoughts on how to go about this? I am new to this. Thanks anyone for any thoughts or insights!

  36. Gautam
    December 9, 2011

    Hello,

    I've got a problem while logging in. After putting in my password and username, i get an error on the same page:

    Warning: mysql_fetch_array() expects parameter 1 to be resource, boolean given in C:\xampp\htdocs\xxxxxx\functions.php on line 190

    What could this mean?

  37. December 10, 2011

    really nice share

    But i didnt got the session and cookie objects how to create and access them

Trackbacks/Pingbacks