Creating an Admin Section with CakePHP

Admin Section with CakePHP

UPDATE: This article has been updated, check out the new version right away

With most database driven websites you will normally need an admin section that requires a username and password that will give you access to a control panel where you can add/edit/delete information. With CakePHP this is quite simple with the help of "admin routing", in the blog tutorial we can access the posts via "http://cakephp/posts/" with admin routing we can separate out all admin functions by adding "http://cakephp/admin/posts/" to the URL.

In this post we are going to re-visit the blog tutorial and implement an admin section that will require a username and password. Any request to an admin specific action will result in a login page being shown if you are not logged in, we will also be using the "Bake" command line script so please go over my previous post if you don't know how to use it.

Enabling Admin Routing

The first thing that needs to be done is to enable admin routing in the "core.php" file located at "/cakephp/app/config/core.php", uncomment the line:

define('CAKE_ADMIN', 'admin');

This will set up the proper URL routing for your applications and will be applied to any URL with 'admin' before the controller action e.g. '/admin/posts/add/' or '/admin/posts/edit/'. This could be set to anything you want however for this example we will stick to default value.

To make use of admin routing there needs to be a completely separate function in the controller for all admin functions and these are created by prefixing 'admin_' to function names, for example 'admin_index()' will be accessed from '/cakephp/admin/posts/' and 'admin_add()' from '/cakephp/admin/posts/add/'. This makes it easier to separate all your admin logic from your regular controller logic. Along with separate functions for your admin section you will also need separate views and these files will be the same name as your controller functions.

Re-Baking the Blog Application

The next step is to re-bake the blog application but this time we will include all the admin functions, fire up the "bake" script in the command line and go through the steps of creating a controller and views for the 'Posts' model. Choose 'yes' when it asks to create methods for admin routing in the controller and choose 'yes' when it asks to create views for admin routing in the views.

Baking the Posts Controller

Open up "posts_controller.php" and you will have twice as many functions, 5 for regular actions and 5 additional functions that are all prefixed with 'admin_', these are your admin functions.

function admin_index() {
	$this->Post->recursive = 0;
	$this->set('posts', $this->Post->findAll());
}

Also look in your posts views folder and you will see a file for each of the admin functions except 'delete' which doesn't require a view. Open up your browser and visit '/cakephp/admin/posts/' to double check that everything is working, we now need to create the functionality for logging in as an admin and to lock down all the admin functions.

Admin Views in CakePHP

Creating the Users Table

We are going to modifying the "simple user authentication" example from the CakePHP Manual so the first thing we need to create is a 'users' table in the database.

CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR( 255 ) NOT NULL ,
`password` VARCHAR( 255 ) NOT NULL ,
`last_login` DATETIME NOT NULL 
);

This table is going to store our username and password, along with the last time we logged in to the admin section. The easiest way to add this information is by using PhpMyAdmin to enter a sample username and password. In this example I am going to be using md5 to create a hashed version of the password for security reasons and I suggest doing this on all your PHP websites. To create your hashed password simply create a test file in your root and view the file in your browser:

echo md5('samplepassword');

Users Model

First create the Users model by creating a file called 'user.php' in the models folder. This is a very simple file and will enable you to connect to the 'users' table in the database.

class User extends AppModel {
	var $name = 'User';
}

Users Controller

Next create a file called 'users_controller.php' in the 'controllers' folder, we will need two functions 'login' and 'logout' which, funnily enough will deal with logging in and logging out an admin user. First the login controller logic, we check that the form has been submitted by inspecting the form data, in CakePHP all the form data is stored in "$this->data". Next we use the 'findByUsername' magic method to get the details of the user from the database with the submitted username. If the user is not empty and the passwords match then the user is legitimate and we go ahead and log them in. At this point if the passwords don't match or no-one was found with that username we set an error that can be used in the view. We are now going to save the user information in a session as a means to check if the admin is valid, then we save the login time and finally redirect to the posts admin index page.

function login() {
	// if the form was submitted
	if(!empty($this->data)) {
		// find the user in the database
		$dbuser = $this->User->findByUsername($this->data['User']['username']);
		// if found and passwords match
		if(!empty($dbuser) && ($dbuser['User']['password'] == md5($this->data['User']['password']))) {
			// write the username to a session
			$this->Session->write('User', $dbuser);
			// save the login time
			$dbuser['User']['last_login'] = date("Y-m-d H:i:s");
			$this->User->save($dbuser);
			// redirect the user
			$this->Session->setFlash('You have successfully logged in.');
			$this->redirect('/admin/posts/');
		} else {
			$this->set('error', 'Either your username or password is incorrect.');
		}
	}
}

The logout function is very simple, it will delete the session that was created when logging in and finally redirect the user to the main posts page. This function does not need a corresponding view file because we do not need to display anything to the user, we just call a redirect.

function logout() {
	// delete the user session
	$this->Session->delete('User');
	// redirect to posts index page
	$this->Session->setFlash('You have successfully logged out.');
	$this->redirect('/posts/');
}

The Login View

Create a folder named 'users' in the views section and inside that create the view for the login action (login.thtml). If the login error is set because of a wrong password then display the error, next display the form with the username and password. I've used form helpers provided by CakePHP to create the labels and form inputs and made sure that the form action is directed to the login action of the users controller ('/users/login/').

<h1>Login</h1>
<?php
// if an error occured display the error message
if(isset($error)) {
	echo "<div class='error'>" . $error . "</div>";
}
?>
<form action="< ?php echo $html->url('/users/login/'); ?>" method="post">
<fieldset>
	<legend>Please Login</legend>
    <p> 
        < ?php echo $form->labelTag('User/username', 'Username:');? >
        < ?php echo $html->input('User/username', array('size'=>'60'));? >
    </p>
    <p> 
        < ?php echo $form->labelTag('User/password', 'Password:');? >
        < ?php echo $html->password('User/password', array('size'=>'60'));? >
    </p>
    <p>
        < ?php echo $html->submit('Login');? >
    </p>
</fieldset>
</form>

When it comes to form data CakePHP uses the 'Controller/fieldname' convention, this makes it very easy to save data from a form and to access the data in the "$this->data['controller']['fieldname']" array.

Protecting your Posts Admin Area

In order to protect the admin area I'm going to create a function that will check the session for the existence of the user data, if the session data is not found then do not allow access to the action by redirecting to the login page. Create a file called 'app_controller.php' in the 'app' folder and code a function called 'checkAdminSession()' any functions that you create in this file will be accessible by any controller by simply calling "$this->methodName()". This works because your controllers all extend AppController and will therefore inherit any function in that file.

function checkAdminSession() {
	// if the admin session hasn't been set
	if (!$this->Session->check('User')) {
		// set flash message and redirect
		$this->Session->setFlash('You need to be logged in to access this area');
		$this->redirect('/users/login/');
		exit();
	}
}

Next we're going to make a check on the session before any admin action in the posts controller, you could add "$this->checkAdminSession()" in every admin function however we can use the "beforeFilter" function that will make the call for us before any controller action is called. To only make this check for admin functions we can check the 'params' array for admin and this will ensure that only admin controller actions will check for a session.

// called before every single action
function beforeFilter() {
	// if admin pages are being requested
	if(isset($this->params['admin'])) {
		// check the admin is logged in
		// this method is in the app_controller.php file
		$this->checkAdminSession();
	}
}

Wrapping Up

Adding a simple admin section for your CakePHP application is generally pretty easy, and because of the joys of MVC (Model, View, Controller) you can easily copy and paste most of the files to other projects your working on. The process of coding something in CakePHP is usually the same, create the database table, then the model, then the controller and finally the views for each controller function.

The biggest hurdle in CakePHP that I found was all the specific methods and conventions that the framework uses but keep experimenting, keep reading the manual and things should hopefully click and fall in to place.

Source Code

Here's the source code for this article, its a zip file containing the app folder of the CakePHP installation. Click here to download code

UPDATE: This article has been updated, check out the new version right away

Posted on 1st February 2008
on 1/2/08

comments powered by Disqus