Tag: cakephp admin routing

User Auth with CakePHP 2.1 – part 3

As promised in the previous part we’ll take a look at the admin section.

If you remember, we’ve setup our users so that when they create an account, they are inactive by default and cannot login into the app.

app/Controller/AppController.php

$this->Auth->authenticate = array(
            'all' => array (
                'scope' => array('User.is_active' => 1)
            ),
            'Form'
        );

Since our freshly created users have is_active = 0 we’ll need to create an admin page where one would be able to approve inactive users.
Let’s see how we’ll act in the admin role.

In part 1, we’ve setup prefix routing and our app is ready to accept admin logins.
This is our login() method again:

public function login() {
            if ($this->request->is('post')) {
                if ($this->Auth->login()) {
                    if ($this->Auth->user('is_admin')) {
                        return $this->redirect(array(
                            'controller' => 'users',
                            'action' => 'index',
                            'admin' => true
                        ));
                    } else {
                        return $this->redirect('/');
                    }
                } else {
                    $this->Session->setFlash(__('Username or password is incorrect'), 'default', array(), 'auth');
                }
            }
        }

And here’s a relevant view, just in case:

<?php
    echo $this->Form->create();
    echo $this->Form->inputs(
        array(
            'username',
            'password'
        )
    );
    echo $this->Form->end('Submit');
    echo $this->Html->link('Don\'t have an account? Register now!', array(
        'controller' => 'users',
        'action' => 'add'
    ));
?>

If we look at the redirect() method in the above login() it presumes that we have an admin_index() action.
Let’s create one:

public function admin_index() {
            $users = $this->paginate($this->User, array(
                'User.is_active' => 0
            ));
            $this->set(compact('users'));
        }

The goal is to display (paginate) inactive users, so that an admin can approve them… simple enough.
I’m not going to cover pagination setup here, as it is very simple, covered nicely in the manual and would be beyond what we need consider for now.

Anyways, let’s build a simple view to display our inactive users:

<?php if (isset($users) && !empty($users)) : ?>
   <?php foreach ($users as $user) : ?>
    <div>
        <?php echo $user['User']['username']; ?>
        <?php
            echo $this->Html->link('Activate', array(
                'controller' => 'users',
                'action' => 'user_activate',
                'admin' => true,
                 $user['User']['id']
            ));
        ?>
        <?php
            echo $this->Html->link('Edit', array(
                'controller' => 'users',
                'action' => 'user_edit',
                'admin' => true,
                 $user['User']['id']
            ));
        ?>
    </div>
   <?php endforeach; ?>
<?php endif; ?>

Next to each username we’ll show an “Activate” and “Edit” links.
Ultimately the “Activate” link we’ll be something like example.com/admin/users/user_activate/345. This should change the user status with ID = 345 from inactive to active.
Here’s the method to do so:

public function admin_user_activate($id = null) {
            if ($id) {
                $this->User->id = $id;
                if ($this->User->saveField('is_active', 1)) {
                    return $this->redirect(array(
                        'controller' => 'users',
                        'action' => 'index',
                        'admin' => true
                    ));
                }
               
            }
        }

All we are doing is updating is_active field to “1″ for a given user $id.
Now the user has been approved and we redirect the admin back to the index page.

Let’s recap:

  1. We’ve setup basic Auth to allow users to login and register in the system
  2. We’ve added a check so that only active users can login
  3. We’ve setup admin/prefix routing to allow for creation of admin-only resources
  4. We’ve added a check so that only admins can access the above resources
  5. And finally, we’ve added the ability for admins to activate the users

p.s. Here’s a simple chunk of code that creates a “Log in/Log out” link so that users can act accordingly. You’ll probably want to add this to your layout or a relevant element.

<?php
echo $this->Session->check('Auth.User')
?
$this->Html->link(
             'Log out',
              array(
                 'controller' => 'users',
                 'action' => 'logout',
                 'admin' => false
              ))
:
$this->Html->link(
              'Log in',
               array(
                  'controller' => 'users',
                  'action' => 'login'
               ));
?>

With a simple check for presence of the Auth.User key (this is where Auth stores information about logged-in users) we know if the user is logged-in or not into the system, and by using a quick ternary operator we display either a “Log out” or “Log in” links.

The end.

Getting Auth and Admin Routing to play nicely together

Here I’m going to show how to use CakePHP’s built-in Auth component and Admin Routing to easily build a protected, administrative area for your application.

I guess this post is somewhat on an intermediate level and I hope you know a little about Auth (or maybe even here) and Admin/Prefix Routing already…

Generally speaking, Auth is often used to allow one to create a web application with multiple user profiles (or accounts… registration, etc.).

However, sometimes, you could have a site, where no user registration is required, but a site owner (admin) needs to have their own control panel (or administrative area) where she can manage some aspects of the site.
This is not to be misunderstood that the two are mutually exclusive, but in this post I’m just going to focus on how to, as always, very quickly (thanks to cake ;)) to build an admin area.

Let’s imagine some web site where there is an Administrator who can add articles to her blog, browse a DB of contacts who’ve submitted questions or comments via some form on the site and do some other, generic, administrative duties.

Even though we won’t have any registered users on the site, to make life just a little bit easier we’ll still create users table, User Model, and Users Controller to handle our admin’s login, logout and homepage. (By default Auth works nicely with the convention of User/users model, controller, table, etc.)

Keeping in mind our Prefix/Admin routing… which we’ve already enabled in core.php Configure::write(‘Routing.admin’, ‘admin’);… we create a Users controller that looks something like this:

<?php
    class UsersController extends AppController {

        var $name ='Users';

        function admin_index() {
            // home page code... could be something as simple as 'Welcome Home' view.
        }

        function admin_login() {
            if($this->Auth->user()) {
                $this->redirect(array('controller'=>'users', 'action'=>'admin_index'));
            }
        }

        function admin_logout() {
           $this->Session->del('Auth.User');
           $this->redirect(array('controller'=>'pages', 'action'=>'display', 'home', 'admin'=>false));
        }
    }
?>

Looks rather simple…
Some might say where’s your beforeFilter() and Auth component? … we’ll handle that in the app_controller.php (just a little later).

Let’s take a look, first, at admin_login().
It could be an empty action, really, but I prefer to override default login() mechanism and basically say, that if the admin has been authenticated, aka $this->Auth->user() returns true, let’s direct her to the home page.

Now what about admin_logout()?
The most common way to build a logout() action is to do something like this:

function logout() {
   $this->redirect($this->Auth->logout());
}

Doing something like this is completely fine, but for my specific need and idea (plus an example of an alternative option to think about) I would rather redirect the admin back to the main page of the site.

Since we cannot rely on the automagic of $this->Auth->logout(), we need to “properly” log out the admin. In other words, we make sure that the session with the default key of ‘Auth.User’ is gone… hence: $this->Session->del(‘Auth.User’);. Then we simply redirect to the default homepage…
Quickly note the ‘admin’=>false in the redirect code… that simply gets rid of the /admin/ prefix in the URL (again one of the lovely cake’s prefix routing tricks).

Alright, so we’ve got the admin login, homepage and logout handled nicely…

Let’s do an amazingly simple Auth component setup in App Controller (app_controller.php)

<?php
class AppController extends Controller {

    var $components = array('Auth');

    function beforeFilter () {
        $this->Auth->autoRedirect = false;
    }

    function beforeRender () {
        $admin = Configure::read('Routing.admin');

        if (isset($this->params[$admin]) && $this->params[$admin]) {
            $this->layout = 'admin';
        }
    }
?>

Well, let’s see… we’ve included our Auth component and added a simple beforeFilter().
As mentioned before, I really didn’t want the admin to be redirected anywhere, except the actual administrator homepage upon login. Therefore, I override the default Auth behavior to redirect to the previously accessed page (aka $this->referer()) by using $this->Auth->autoRedirect = false;

Why?
Imagine a blog… and an admin decided to login from a link in the footer of the site from some random blog article. Obviously the admin wants to get to her admin area and not just get redirected back to the article. (Of course this is highly dependent on the application, but in my case that was a requirement and this apporach worked out quite nicely).

To be honest I’m not quite sure about the beforeRender() method, but it did the trick to switch the layout from ‘default’ to ‘admin’ based on the action name (again, as you see, ‘admin_’ is a built-in part of cake’s Prefix/Admin routing).
(Thanks for a nice solution provided by Mark Story for the layout switching).

Well, that’s all lovely, but what’s next?
As a matter of fact we’re pretty much done. Our app is ready to easily setup any administrative functions for any of your controllers.

Alright, let’s say we’ve got a Posts controller and we need to make one of our actions (let’s imagine /admin/posts/add) for administrator only…

All that needs to be done is the following:

<?php
   class PostsController extends AppController {

       var $name ='Posts';

       function beforeFilter() {
          parent::beforeFilter();

          $this->Auth->allow('index', 'view');
        }

       function index() {
         //something here
       }

       function view() {
       //something here too
       }

        function admin_add() {
            if(!empty($this->data)) {
                $this->Post->save($this->data);
            }
        }
    }
?>

First, we inherit our App Controller’s settings by using parent::beforeFilter();, then we ensure that regular/non-registered users can see (access) index() and view() actions.

Of course, our admin_add() action is nicely protected and would only be accessed if the admin had authenticated.

Now what do you do if you needed an administrative action in the Contacts Controller, that allows an admin to ‘browse’ contacts?…

Yep, you simply add admin_browse() to your Contacts Controller. Of course, as in the example above, any actions of this controller that need to be seen by regular users would need to be allowed in beforeFilter() as we just did.

(I guess there is no need to mention that all admin actions should be added to the admin layout or element as links for convenient navigation ;))

P.S. How do you add an admin account to the users table with a properly hashed password?
The easiest way, unless you wish to build an add_admin() action is to temporarily enable scaffolding, which will let you easily add new users (admins).