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).

48 thoughts on “Getting Auth and Admin Routing to play nicely together

  1. in beforeRender() you could also use:

    $admin = Configure::read(‘Routing.admin’);
    if (isset($this->params[$admin]) && $this->params[$admin]) {
    $this->layout = ‘admin’;
    }

  2. Another great everyday example! Good to see it published. It really is something easy, when you know how to use it. If you don’t it will take you hours of routing and redirecting.
    Good work!

  3. @Phally and Mark Story

    Phally, as mentioned thanks for reminding about that important, little detail ;) And you are right it took me hours staring at random code and quite a few tries, before I got it all narrowed down… so, like snowboarding, it’s very easy once you know how to do it, but you’ll get your neck twisted by the time you get there :)

    Thanks, Mark… ctrl+c/ctrl+v… here I come! :)

  4. You could avoid having to allow actions in every controller by adding this to your AppController’s beforeFilter():

    $admin = Configure::read(‘Routing.admin’);
    if (isset($this->params[$admin]) && $this->params[$admin]){
    $this->layout = ‘admin’;
    }
    else {
    $this->Auth->allow();
    }

    I’ve used the Auth + Admin Routing system in many projects and I find it very useful. The only thing I feel that is not so good is that I can’t find a way to keep it DRY. I mean, every action has to start with “admin_”, and every view file name must start with “admin_” as well. Suppose I want to change the prefix to “administration”. I’ve got to change things everywhere!

  5. Your timing could _not_ have been better. I started trying to use the Auth component yesterday and was definitely, as you put it, getting my neck twisted. All I was looking for is a simple boolean-esque login and you nailed it. I’ll be digging deeper into some of what you’re doing here, but this answered at least one lingering question immediately.

    Thanks.

  6. @Rob Wilkerson

    Well, I’m glad it helped out.
    You might also want to check some of my earlier tutorials on Auth, which cover some of the gotchas, tricks and tips.

  7. @Javier

    Very good idea (and thanks for sharing), but in my particular case I had to be a little more explicitly strict due to some other aspects of the application.

  8. To add to Mark Story’s comment, I usually require a bit more logic for admin only and non-admin only sections, so I do the following:

    function beforeFilter()
    {
    if ($this->isAdmin()) {
    $this->beforeAdminFilter();
    } else {
    $this->beforeFrontFilter();
    }
    }

    function isAdmin()
    {
    $admin = Configure::read(’Routing.admin’);
    return (isset($this->params[$admin]) && $this->params[$admin]) ? true : false;
    }

    I’ll do the same for beforeRender. Just an easy way for me to separate the admin logic from the frontend logic.

  9. hi

    i start with cakeview and i search to do an admin section…

    does somebody can say me what i suppose to put in admin_index function?

    direct html?

    i don’t really understand the “Welcome Home’ view. ”

    thanks

  10. @redfox26

    Imagine that you are an admin… what would you like to see on that page?
    A summary of some stats, a naked girl, and maybe a joke of the day…

    Bottom line, anything that you feel would be worthwhile for the admin is good to go ;)

  11. @redfox26: It would work the same way your frontend home page looks like. Some people make it just html with a welcome message, others may want to put in different kind of “widgets” pulling data from different models as a portal to other sections.

    Another thing you can do is simply redirect to particular action if you don’t want a default home page.

  12. Awesome article. I’m building my first real cake application, and this is perfect.

    However, I’m running into a problem with your code.

    Your users/admin_login method is pretty cool, but I think this line
    $this->redirect(array(‘controller’=>’users’, ‘action’=>’admin_index’));

    needs to be rewritten to
    $this->redirect(array(‘controller’=>’users’, ‘action’=>’index’, ‘admin’=>TRUE));

    Again, I’m pretty new to cake, so I could be wrong, but this seems more correct.
    Thanks.

  13. nice page teknoid. I found this quite illuminating.

    I am doing a site where I want to use admin routing and I also have regular users, as you allude to in your post. This is what I have done – not sure if it is a good way or not, but seems to work for me, in allowing users to get access to non-admin pages, guests to access all guest-accessible-pages and admins to the admin_ pages.

    function beforeFilter(){
    $this->Auth->loginAction = ‘/users/login’ ;
    $this->Auth->allowedActions = array(‘display’);

    //in case we have an admin page requested:
    if ( isset($this->params[Configure::read(‘Routing.admin’)]) && $this->params[Configure::read(‘Routing.admin’)]) {
    if ($this->isAdmin($this->Auth->user())) {
    //$this->layout = ‘admin’;
    var_dump(‘admin layout’);
    }
    else {
    $this->Auth->deny();
    }
    }

    }

    function isAdmin($authuser){
    // var_dump(Set::extract($authuser, ‘User.group_id’)) ;
    return (Set::extract($authuser, ‘User.group_id’) === “1”) ? true : false ;
    }

  14. hmm, it doesnt work for the users who are logged in but not admins – it still shows the page but not the layout. I think the Auth->deny () gets a over-ride later. I shall adjust and return shame-faced to my drawing board.

  15. @luke

    Hi, thanks for sharing. The only thing I’m not sure about is var_dump(‘admin_logout’); … probably a left over debug statement?
    Also, it’s best to write ‘/users/login/’ in cake-like array notation.

    Other than that, seems fine to me.

    p.s. I’ve updated your comment to remove the “extra” code.

  16. hey teknoid – yes, I usually remove them once I’m happy with something working (textmate find all and then zap)

    good point about the array notation.

  17. @luke

    Just a quick fyi… if you use cake’s pr(), which is essentially a wrapper for print_r() then it won’t produce any output while you are in debug = 0 mode (production). So if you do accidentally forget to remove one pr() here or there in production it’s a safer bet then var_dump().

  18. How can we approach if we have registered users as well with administrator. Auth component will fail in that case ?

  19. @Chirayu

    Why would it fail?
    You should probably have a ‘role’ field in your users table, you can then do a check such as:
    if($this->Auth->user(‘role’)) { …

    This is partially covered in the manual.

  20. In both Mark’s and Steve’s comments, why is the comparison of $this->params[$admin] repeated?

    isset($this->params[$admin] && $this->params[$admin])

    This is throwing a php error for me. However, when I express it like this

    isset($this->params[$admin])

    it works fine.

  21. @foldi

    It’s not really “repeated”… one is checking if the param is actually set and the other one is checking if it’s set to ‘true’. That being said, there seems to be a missing or misplaced ‘)’ in the if()

  22. Hi, i have problem, i get message from my browser (Firefox) like as “Redirect Loop Error”, why? Sorry, but my english it’s not perfect, and lot of things i can’t understand…

  23. @kicaj

    I’ve seen the problem happen once in a while, but when exactly does it take place? When you try to login/logout… or all the time when you visit homepage, for example?

  24. When I set in app_controller in components ‘Auth’, and use $this->Auth->allow(‘some’, ‘functions’); or $this->Auth->allowedActions()

    I think, I can’t use $this-Auth… in app_controller

  25. @kicaj

    You certainly can use it in App Controller.
    Usually this happens when you try to login/logout and leads you back to the page you came from, which creates a infinite loop.

    That being said, it’s best to specify which actions you are “allowing” in the specific controller.

  26. I have next problem, hehe again:p

    When i add ‘language’ to my url (eg. in form action) i can’t loggin to admin area, why?
    I use app_helper to add language, it’t very simple…

  27. Hi techno,

    I went through your tutorial. I had a query on using auth component for Registered users as well as Admin users. You replied that its possible if your user table has the role field.

    I am using table structure which has Many to Many relations with User and Role and maintained into separate table called users_roles. Typically I have followed the structure posted here : http://www.studiocanaria.com/articles/cakephp_auth_component_users_gro

    can you suggest in brief what should be the approach, or some code example.

    1. @Chirayu : i’m using the same technique, but the problem is if an admin trying to login using admin params it’s always shows flash messages ‘is not authorize’ , can you help? :)

  28. I’d like to add my thanks for this great article.

    I had hoped to construct my app. so that I had one login form /users/login/ and then to use logic in the login action to decide if the user was an administrator and redirect them to /admin/users/whatever/.

    Is there a way to do this? All my attempts came to nought so for now I’ve used your method of having a separate admin login /admin/users/login/

  29. First of all, thanks for the article, very simple concept and I really like it :).

    Just a quick tip for anyone out there who require your user authentication as well as admin and want to deny access to any user trying to use any admin prefixed link.

    1) You need to create a field in your users table called ‘role’. Then either make a user an ‘admin’ or ‘user’ (Or use whatever you prefer.).

    2) In your AppController::beforeFilter() add this bit of code at the bottom

    $admin = Configure::read(‘Routing.admin’);
    if (isset($this->params[$admin]) && $this->params[$admin] && $this->Auth->user(‘role’) != ‘admin’)
    {
    $this->Session->setFlash(‘You are not authorized.’, ‘default’, array(‘class’ => ‘error’));
    $this->redirect(‘/’);
    }

    Basically what this does is check to see if the users role is admin and redirects to the home page if they are not. For added security remove the flash message and redirect and serve up a 404 error so the user doesn’t know they found an admin link.

  30. I had some problems with your code in cakePHP 1.3

    However, with a bit of head scratching, I got your beforeRender() function to work by doing the following:

    function beforeRender () {
    // $admin = Configure::read(‘Routing.admin’);
    //This is no longer required, but in 1.3 it should read $admin = Configure::read(‘Routing.prefixes’, array(‘admin’))

    //It appears that as long as you have set up admin routing in the config/core.php file, this code works….I used Bake to create mine :)

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

    I’m finding the changes from 1.2 to 1.3 rather interesting at present, especially as a noob, so hopefully this will save someone else hours of frustration!

  31. @tricky999

    That’s true… 1.3 only has prefix routing. So unless you manually add Configure::write(‘Routing.admin’); (WHICH YOU REALLY SHOULD NOT) the above is not going to work on 1.3

    Prefix routing is the way to go in 1.3. Thanks for sharing.

Leave a reply to teknoid Cancel reply