Monday, September 23, 2013

Auth module for Kohana 3.3



First you have Kohana 3.3 installed with the Auth, Database, and ORM modules enabled and configured.you can enabled these modules on bootstrap.php file.

In your APP_PATH/config directory your auth.php configuration file needs to have the driver set to use the ORM, and a hash_key must be specified. Here is an example of a complete auth.php config file:

Auth configuration: config/auth.php

<?php defined('SYSPATH') or die('No direct access allowed.');

return array(

            'driver'       => 'orm',
            'hash_method'  => 'sha256',
            'hash_key'     => 'Never gonna give you up',
            'lifetime'     => 1209600,
            'session_key'  => 'auth_user'

);
?>

After completing this, you will want to add the necessary database tables to allow the ORM driver to function properly.

CREATE TABLE IF NOT EXISTS `roles` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `description` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `uniq_name` (`name`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

INSERT INTO `roles` (`id`, `name`, `description`) VALUES(1, 'login', 'Login privileges, granted after account confirmation');
INSERT INTO `roles` (`id`, `name`, `description`) VALUES(2, 'admin', 'Administrative user, has access to everything.');

CREATE TABLE IF NOT EXISTS `roles_users` (
  `user_id` int(10) UNSIGNED NOT NULL,
  `role_id` int(10) UNSIGNED NOT NULL,
  PRIMARY KEY  (`user_id`,`role_id`),
  KEY `fk_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `email` varchar(127) NOT NULL,
  `username` varchar(32) NOT NULL DEFAULT '',
  `password` varchar(64) NOT NULL,
  `logins` int(10) UNSIGNED NOT NULL DEFAULT '0',
  `last_login` int(10) UNSIGNED,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `uniq_username` (`username`),
  UNIQUE KEY `uniq_email` (`email`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `user_tokens` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_id` int(11) UNSIGNED NOT NULL,
  `user_agent` varchar(40) NOT NULL,
  `token` varchar(40) NOT NULL,
  `type` varchar(100) NOT NULL,
  `created` int(10) UNSIGNED NOT NULL,
  `expires` int(10) UNSIGNED NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `uniq_token` (`token`),
  KEY `fk_user_id` (`user_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

ALTER TABLE `roles_users`
  ADD CONSTRAINT `roles_users_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `roles_users_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE;

ALTER TABLE `user_tokens`
ADD CONSTRAINT `user_tokens_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE;




Start by creating a new controller where we can create actions to demonstrate how to use the module in real code. For this example, we will create a User controller with actions for creating an account, logging in and out, and viewing user info when logged in.
 
User Controller: classes/controller/user.php

<?php defined('SYSPATH') or die('No direct script access.');

class Controller_User extends Controller_Template {
 
public $template = 'site';

            public function action_index()
            {
                        $this->template->content = View::factory('user/info')
                                    ->bind('user', $user);
                       
                        // Load the user information
                        $user = Auth::instance()->get_user();
                       
                        // if a user is not logged in, redirect to login page
                        if (!$user)
                        {
                                    Request::current()->redirect('user/login');
                        }
            }

            public function action_create()
            {
                        $this->template->content = View::factory('user/create')
                                    ->bind('errors', $errors)
                                    ->bind('message', $message);
                                   
                        if (HTTP_Request::POST == $this->request->method())
                        {                                 
                                    try {
                       
                                                // Create the user using form values
                                                $user = ORM::factory('user')->create_user($this->request->post(), array(
                                                            'username',
                                                            'password',
                                                            'email'                                     
                                                ));
                                               
                                                // Grant user login role
                                                $user->add('roles', ORM::factory('role', array('name' => 'login')));
                                               
                                                // Reset values so form is not sticky
                                                $_POST = array();
                                               
                                                // Set success message
                                                $message = "You have added user '{$user->username}' to the database";
                                               
                                    } catch (ORM_Validation_Exception $e) {
                                               
                                                // Set failure message
                                                $message = 'There were errors, please see form below.';
                                               
                                                // Set errors using custom messages
                                                $errors = $e->errors('models');
                                    }
                        }
            }
           
            public function action_login()
            {
                        $this->template->content = View::factory('user/login')
                                    ->bind('message', $message);
                                   
                        if (HTTP_Request::POST == $this->request->method())
                        {
                                    // Attempt to login user
                                    $remember = array_key_exists('remember', $this->request->post()) ? (bool) $this->request->post('remember') : FALSE;
                                    $user = Auth::instance()->login($this->request->post('username'), $this->request->post('password'), $remember);
                                   
                                    // If successful, redirect user
                                    if ($user)
                                    {
                                                Request::current()->redirect('user/index');
                                    }
                                    else
                                    {
                                                $message = 'Login failed';
                                    }
                        }
            }
           
            public function action_logout()
            {
                        // Log user out
                        Auth::instance()->logout();
                       
                        // Redirect to login page
                        Request::current()->redirect('user/login');
            }

}
?>


After that we have to create simple view scripts to display content or functionality inside our template.


Template: application/views/site.php

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="description" content="Example Auth with ORM for Kohana 3.3" />
<title>Auth with ORM</title>
</head>
<body>
            <h1>Auth module for Kohana 3.3</h1>
            <div id="content">
                        <?= $content; ?>
            </div>
</body>
</html>

Now we can create simple view scripts of each action on controller and display them inside our template. This will make the rest of the views small, and simple to read. In the controller we are loading the user/create view into the $content view variable, so let's create a new folder in our view directory named users and add the following views:

User signup view: create.php

<h2>Create a New User</h2>
<? if ($message) : ?>
            <h3 class="message">
                        <?= $message; ?>
            </h3>
<? endif; ?>

<?= Form::open('user/create'); ?>
<?= Form::label('username', 'Username'); ?>
<?= Form::input('username', HTML::chars(Arr::get($_POST, 'username'))); ?>

<?= Form::label('email', 'Email Address'); ?>
<?= Form::input('email', HTML::chars(Arr::get($_POST, 'email'))); ?>

<?= Form::label('password', 'Password'); ?>
<?= Form::password('password'); ?>

<?= Form::label('password_confirm', 'Confirm Password'); ?>
<?= Form::password('password_confirm'); ?>

<?= Form::submit('create', 'Create New User'); ?>
<?= Form::close(); ?>



Login view: login.php

<h2>Login</h2>
<? if ($message) : ?>
            <h3 class="message">
                        <?= $message; ?>
            </h3>
<? endif; ?>

<?= Form::open('user/login'); ?>
<?= Form::label('username', 'User name'); ?>
<?= Form::input('username', HTML::chars(Arr::get($_POST, 'username'))); ?>
<?= Form::label('password', 'Password'); ?>
<?= Form::password('password'); ?>
<?= Form::submit('login', 'Login'); ?>
<?= Form::close(); ?>
<p>Or <?= HTML::anchor('user/create', 'create a new account'); ?></p>


View user detail : info.php

<h2>Detail for  user "<?= $user->username; ?>"</h2>

<ul>
            <li>Email: <?= $user->email; ?></li>
            <li>Number of logins: <?= $user->logins; ?></li>
            <li>Last Login: <?= Date::fuzzy_span($user->last_login); ?></li>
</ul>

<?= HTML::anchor('user/logout', 'Logout'); ?>

Please feel free to comment.