Tag: cakephp 2.1

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.

User Auth with CakePHP 2.1 – part 2

CakePHP 2.1

Now that we’ve completed our basic setup for Auth, let’s take a look at the User.php model…

class User extends AppModel {
   
    public $validate = array(
      'username' => array(
          array(
            'rule' => 'notEmpty',
            'message' => 'Username cannot be empty'
          ),
          array(
            'rule' => 'isUnique',
            'message' => 'This username is already taken'
          )          
      ),
      'password' => array(
          array(
            'rule' => 'notEmpty',
            'message' => 'Password cannot be empty'
          ),
          array(
            'rule' => array('minLength', 4),
            'message' => 'Must be at least 4 chars'
          ),
          array(
            'rule' => array('passCompare'),
            'message' => 'The passwords do not match'
          )
      )
    );
   
    public function passCompare() {
        return ($this->data[$this->alias]['password'] === $this->data[$this->alias]['password_confirm']);        
    }
   
    public function beforeSave() {
        $this->data['User']['password'] = AuthComponent::password($this->data['User']['password']);
        return true;
    }
}

Nothing terribly interesting… we’ve got our validation rules setup and just a few simple methods to handle the rest.
Let’s go in reverse a little. Keep in mind that the new Auth system in CakePHP doesn’t hash passwords by default. This is actually great news for old-timers, because the work-arounds and somewhat hacky solutions we had to do in 1.x (just to deal with password comparison, for example) are gone and are now replaced with a single line of code in your beforeSave() method.
(I hope you see which one). By using AuthComponent::password() we encrypt the user’s password with a default hashing algorithm.
Thus the password is safely hashed in the DB and we do so just prior to saving the record (remember to always return true;) in the beforeSave() or nothing will be… saved).

Speaking of password comparison, you’ll notice that in our validation rules we have a custom method passCompare(). Because I’m lazy and too much typing leads to headaches, a simple one liner will take care of our needs. If passwords match the method will return true, else it will return false and that’s all we really need to validate or invalidate the given field.

For now this covers our User.php model.

Let us take a look at some interesting things in the UsersController.php.

public function beforeFilter() {
    parent::beforeFilter();
    $this->Auth->allow(array(
       'add', 'account_created'
    ));
}

We will allow two methods in our UsersController.php to be accessible by non-authorized (not logged-in) users.
Of course, it would be silly not to allow users to register their account, thus we allow add() method… also we’ll have a simple action account_created(), which is not going to do much of anything for the time being.

Alright, let’s take a look at the relevant add() method:

public function add() {
            if ($this->request->is('post')) {
                if ($this->User->save($this->request->data)) {
                    $this->Session->setFlash(__('Account created. An admin will need to activate it.'), 'default', array(), 'auth');
                    return $this->redirect(array(
                        'controller' => 'users',
                        'action' => 'account_created'
                    ));
                }
            }
        }

All we do here is accept the data from a form, validate it (by using the save() method) and if all goes well, we redirect the user to the “account created” page.
Remember all that prefix/admin routing we’ve setup before?
Well, as you can see our user is going to live in the system, but remain inactive until an admin logs-in and activates her. Recall ‘scope’ => array(‘User.is_active’ => 1)… freshly created account will have is_active = 0, so without admin approval nobody is getting in.

Let’s take a quick look at the add.ctp view:

<?php
    echo $this->Form->create();
    echo $this->Form->inputs(
        array(
            'username',
            'password',
            'password_confirm' => array(
                'type' => 'password'
            )
        )
    );
    echo $this->Form->end('Submit');
?>

As you can see I really went all out here… three fields: username, password, and password_confirm. Now compare these fields to the validation rules in our User.php model and you should see how the whole thing is coming together. I kept the example purposely oversimplified to just show how the data will be POST’ed from the form to the controller’s add() method, validated by the User.php model (with password hashing) and thereafter saved to the DB.

To wrap things up for part 2 of this tutorial, let’s take a look at the login() and logout() methods (also in the UsersController.php).

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');
                }
            }
        }

The interesting thing about this method is the check for admin. if ($this->Auth->user(‘is_admin’)) { … , so if the user is an admin we redirect them to example.com/admin/users/index (this is all part of prefix/admin routing). To tell cake, which “route” it should take we supply ‘admin’ => true. Of course, this presumes that we’ll have an admin_index() method in the UsersController.php. As you see, all other users simply get redirected to the “/” (root) of your website.

The logout() method is as simple as could be:

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

In the next part we will take a look at the admin side of things and some little things like a login/logout link, which you’d expect in any site that has user Auth.

User Auth with CakePHP 2.1 – part 1

CakePHP 2.1

The example application in the CakePHP book does a very good job of covering setup and implementation of a basic Auth system.
Let’s continue building on that and cover a few other things, by adding a couple of more features and looking at some of the other things in more detail.

Good to say that cake started separating authentication and authorization as of 2.x release. While these concepts always go hand-in-hand, I feel it created a bit of confusion for beginners, because while separate in theory they were not clearly separate in implementation.

As always, your main players when it comes to Auth is AppController.php and User.php.
(Of course, UsersController.php is not to be forgotten about…)

Let’s go ahead and start with app/Controllers/AppController.php:

class AppController extends Controller {
    public $components = array('Auth', 'Session');
   
    public function beforeFilter() {
        $this->Auth->authorize = array('Controller');
        $this->Auth->authenticate = array(
            'all' => array (
                'scope' => array('User.is_active' => 1)
            ),
            'Form'
        );
    }
   
    public function isAuthorized($user) {
        if (($this->params['prefix'] === 'admin') && ($user['is_admin'] != 1)) {
            return false;            
        }
        return true;
    }
}

First, as always we will include the necessary components. For the time being it’s just: public $components = array(‘Auth’, ‘Session’);.

Authorization is going to be controller-based. Meaning, we will tell CakePHP to authorize (let users access resources) based on the Controller actions.
(It’s nice that you can now specify such setting directly in the $components array, but I kept it in the beforeFilter() to show a slightly different approach).

Next, comes our Authentication setup. Unlike Authorization, which answers who is allowed to get to what, Authentication checks if the user is indeed who she claims to be (handle login/logout).

We’ll be using good ol’ login form, thus the ‘Form’ key in our setup.
Moving on, I’ve added a “scope” of User.is_active => 1… this presumes that in our users table we have a field called is_active and therefore only active users can access the application (everybody whose is_active status is equal to 0 is denied by default). More on this a little further.

isAuthorized() is a sweet little method, which helps us to fine-tune our permissions.
The implementation is truly up to your needs, but let’s see what we’ve got going on in this example…

public function isAuthorized($user) {
   if (($this->params['prefix'] === 'admin') && ($user['is_admin'] != 1)) {
      return false;            
   }
   return true;
}

The basic premise here is that an admin can access admin-related resources within the app. (We’ll get into prefix routing setup a little further down the line).
The presumption is that we have an is_admin field in our users table and if the user is not an admin $user['is_admin'] != 1 she cannot access any resource that has an “admin” prefix… again more on that a little later.
Otherwise, for all average Joe’s, we say return true; access whatever you want (once authorized) as long as it’s not an admin-related resource.

Alright then, let’s take a look at the prefix routing.
The setup couldn’t be simpler just un-comment the following in your app/Config/core.php.
Configure::write(‘Routing.prefixes’, array(‘admin’));

This concludes our basic setup. We’ll take a look at the User.php model as well UsersController.php in the next part, which is coming soon here.

p.s. For those who wish to play around with the setup, here’s all you’d need to get started with the users table.

[sql]
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
`password` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
`is_admin` tinyint(1) DEFAULT ’0′,
`is_active` tinyint(1) DEFAULT ’0′,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
[/sql]