Auth module for Kohana 3.1 using ORM Driver

26 Mar 2011

One of the changes to the 3.1 release of Kohana has been the removal of the ORM driver as part of the official Auth module.  To avoid the confusion that the Auth module required the ORM module, Auth now ships with only a file driver.

This has left some people under the impression that in order to use the ORM, porting a driver is necessary.  This is not the case, in fact, the ORM Auth driver is now living in the ORM module with all the database schema examples in MySQL and PostgreSQL.

To get started, you are first going to need to have Kohana 3.1 installed with the Auth, Database, and ORM modules enabled and configured. 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'

);
?>

Once this is accomplished, you will want to add the necessary database tables to allow the ORM driver to function properly.

Supplied in the ORM module, you will find the example database schemas, and you can view them here for MySQL and PostgreSQL. These are basic, example schemas that show the core functionality, and these can be used as foundations to build your final user authentication solution.

For this tutorial, we will be using MySQL for our database. If you are using Postgres, there should not be too many differences as the ORM is database agnostic.

The schema can be found at https://github.com/kohana/orm/blob/3.1%2Fmaster/auth-schema-mysql.sql or you can copy and paste from below:

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;

Looking over the public methods for the Kohana_Auth_ORM class let's us see the basic functionality that is included in the driver:

These methods cover all of the necessary functionality to get user authentication up and running in an application. For this tutorial, we will construct a controller with a basic set of actions and views to allow the creation, login, logout, password reset, and logout actions for a user. A quick glance at the Model_Auth_User class supplied with the ORM reveals that most of the heavy lifting has been done for us.

We can begin 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. This example does support the "remember me" functionality.

User Controller: classes/controller/user.php

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

class Controller_User extends Controller_Template {

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

}
?>

As you see here, we are using the Template controller to make it simple to display our views. This is an example template that is included in the source files for this tutorial:

Template: views/template.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.1" /> 
<meta name="author" content="JDStraughan" /> 
<meta name="copyright" content="Copyright 2011. JDStraughan.com" />
<meta name="language" content="en-us" /> 
<title>Auth with ORM tutorial for Kohana 3.1</title> 
<style type="text/css">
.error {
	color: red;
}
.message {
	padding: 10px;
	background-color: yellow;
}
</style>
</head> 
<body>
	<h1>Auth module using the ORM driver for Kohana 3.1</h1>
	<div id="content">
		<?= $content; ?>
	</div>
</body>
</html>

Now we can create simple view scripts 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'))); ?>
<div class="error">
	<?= Arr::get($errors, 'username'); ?>
</div>

<?= Form::label('email', 'Email Address'); ?>
<?= Form::input('email', HTML::chars(Arr::get($_POST, 'email'))); ?>
<div class="error">
	<?= Arr::get($errors, 'email'); ?>
</div>

<?= Form::label('password', 'Password'); ?>
<?= Form::password('password'); ?>
<div class="error">
	<?= Arr::path($errors, '_external.password'); ?>
</div>

<?= Form::label('password_confirm', 'Confirm Password'); ?>
<?= Form::password('password_confirm'); ?>
<div class="error">
	<?= Arr::path($errors, '_external.password_confirm'); ?>
</div>

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

<p>Or <?= HTML::anchor('user/login', 'login'); ?> if you have an account already.</p>

Login view: login.php

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

<?= Form::open('user/login'); ?>

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

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

<?= Form::label('remember', 'Remember Me'); ?>
<?= Form::checkbox('remember'); ?>

<p>(Remember Me keeps you logged in for 2 weeks)</p>

<?= Form::submit('login', 'Login'); ?>
<?= Form::close(); ?>

<p>Or <?= HTML::anchor('user/create', 'create a new account'); ?></p>

User info view: info.php

<h2>Info 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'); ?>

Here we have a basic form, with an area to display a success or failure message, sticky form fields, and error messages displayed for each user. We are almost ready to begin creating users, right after we complete the custom messages for validation failures. In the controller actions we specified the location of our messages in the models directory, so you will need to create a new directory named models in your messages directory inside your APP_PATH and add a file named user.php with the following contents:

<?php

return array(
	'username' => array(
        'not_empty' => 'You must provide a username.',
        'min_length' => 'The username must be at least :param2 characters long.',
        'max_length' => 'The username must be less than :param2 characters long.',
        'username_available' => 'This username is already in use.',
    ),
	'email' => array(
		'not_empty' => 'You must enter an email address',
		'min_length' => 'This email is too short, it must be at least :param2 characters long',
		'max_length' => 'This email is too long, it cannot exceed :param2 characters',
		'email' =>	'Please enter valid email address',
		'email_available' => 'This email address is already in use.',
	)
);
?>

This covers almost all the messages for validators in the Model_Auth_User class, but there still is the _external validator for password confirmation. To provide a custom message for that also, you will need to crete a new directory named user inside messages/models (so the path is APP_PATH/messages/models/user), and add a file named _external.php with the following contents:

<?php

return array(
	'password' => array(
		'not_empty' => 'Please choose a password',
	),
    'password_confirm' => array(
        'matches' => 'The password fields did not match.',
    ),
);

You are now ready to begin adding users to this example application, complete with remember me functionality. Adding additional features like updating user information, changing passwords, or adding more user information should all be relatively simple.

The source code for this tutorial can be downloaded here. Please feel free to use the comment section below to post any questions or concerns.

Comments (30)
These comments are frozen, as they are archived from my previous blog.

gravatar
david
about a year ago

You made a little mistake. array_key_exists already returns a boolean so there is no need for a ternary operator.

Otherwise a great article!

gravatar
JDStraughan
about a year ago

@david: Thanks! I have made the correction.

gravatar
Lee
about a year ago

Thanks for the guide... One issue: At least some of the error comments don't show up on the template.

For example, if the user enters too short of a password... I can access that using the following:



But I'm unsure of Kohana's message routing and don't know if that's the correct way to handle it.

gravatar
Lee
about a year ago

Not sure if previous comment made it through.

In the registration action, if you enter too-short of a password, there is no error displayed... I can access it in '_external', like this:

Arr::get($errors['_external'], 'password');

But I'm unclear if that's an acceptable Kohana way of doing it.

gravatar
Adam
about a year ago

Great Article! and thanks for the Rick Roll on line 7 of auth.php Got a good laugh out of that.

gravatar
Alonso
about a year ago

Hi, it's been quite difficult migrate to 3.1

I tried this example but i can't login and want to create a new user have this message:

---
ORM_Validation_Exception [ 0 ]: Failed to validate array
--
MODPATH/orm/classes/kohana/orm.php [ 1233 ]

thanks for sharing this, greetings!

gravatar
JDStraughan
about a year ago

@Alonso: I cannot reproduce this locally, are you sure you have the framework setup correctly? You are welcome to contact me directly using the contact form, and I will be happy to help in any way I can.

Thanks!

gravatar
Cesar Gonzalez
about a year ago

Hey Jason, thank you for sharing this and for the thoroughly written tutorial! I was able to get it working without any problems.

Now I need to extend the user to include some personal information (first / last name, short bio) and some account information ( # of credits). I'm at a loss for how / where I would do this. Do I create another database table and model? Do I extend the existing model and add a few fields to the users table? In each case, where exactly are the relevant files I should work on?

I know my way around PHP / MySQL but I'm new to Kohana and ORM. I'm hoping that you can point me in the right direction and give me some idea of where to start tackling this? Thanks again!

gravatar
Brian Wisti
about a year ago

Well you're my new favorite Kohana blogger ^_^ This is the first auth tutorial I've come across that actually lets me create user and log in and out as the user with 3.1. Thanks very much!

I did come across the same issues that Lee mentioned, though. Both `password` and `password_confirm` were stored in `_external`. `_external` error messages had to be accessed via `Arr::get($errors['_external'], $field)`. Also, the message overrides in `application/messages/user/_external.php` didn't appear to take. It used the default messages for those.

Mind you, I am _not_ complaining. After struggling a day or so with other resources, yours got me a new user within 30 minutes. Thanks again!

gravatar
Fredrik
about a year ago

Fantastic! I'm definitely buying your book once its published :c)

gravatar
Peter Weil
about a year ago

Thank you *so much* for this, Jason - this sort of thing is so badly needed. Perhaps I messed something up, but after I successfully created a new user, I cannot log in (and there is no error message). It just keeps returning me to the login page. However, the log says "ERROR: Error reading session data: " Do you have any ideas as to what's wrong?

(looking forward to your Kohana book!)

gravatar
Peter Weil
about a year ago

Please ignore my last message. The problem was that I had to set a custom read/write-privileged MySQL config to *all* of the models being used by auth, and not just the User model. Sorry to bug you, and thanks again for this tutorial. Kohana is a wonderful framework, but sometimes the lack of good, up-to-date "how-tos" drives me up a wall.

gravatar
youngpac
about a year ago

thanks a bunch for this
its one of the best tutorials i have seen yet...

gravatar
JDStraughan
about a year ago

@Brian, Lee: Sorry for the late reply. I have fixed the password error message, and now show how to properly override and retrieve the error message for this field.

You may want to look at the Arr::path() method as well as Arr::get(), and do note I have included both in the above examples.

@Cesar: I hope you got my email and I hope it answered your questions. I may do a post on ORM relationships in the near future.

@All: Thanks for the kind words, and please continue to ask questions and point out my bugs ;)

gravatar
Yann
about a year ago

Hi !

Thanks for this post, help me a lot.

I'm trying to extends a lot user class of Auth and creating some new ones for adding capabilities like Wordpress ones. But like Cesar, I'm a little lost so can you help me understand how to extends or create new class who can interact with the Auth class ?!

Thank you very much in advance,

Yann

gravatar
Yann
about a year ago

Hi again,

I saw something wrong in a sentence you made "To provide a custom message for that also, you will need to create a new directory named user inside messages/user, and add a file named _external.php"

Your _external.php file must be in messages/models/user/ because you choose to use "models" and not in messages/user/ like you wrote. I think you made a typo.

Hope this help some others ^^

Yann

gravatar
youngpac
about a year ago

hi, am trying to move the code from the use class to my index controller so that the auth can check on login on to the website but whnever i hit the submit button it takes me to user/login while i have remove the class for user any help?

thanks in advance.

gravatar
Peter Weil
about a year ago

Thanks also for the examples using the Arr helper methods. I never realized how useful these are.

If you want to display all of the errors together; e.g., above the form, you can run a check on $errors['_external']['password'] and add it to the $errors array.

if ( ! empty($errors['_external']['password']))
{
$errors[] = $errors['_external']['password'];
}

And then just loop through $errors to display them, e.g.,

if( ! empty($errors)) : ?>

gravatar
JDStraughan
about a year ago

@youngpac, @Yann: You can add to the user table and model, extend the model, or add to the controller. I am not really sure what you need.

@Yann: I made the change, thanks for pointing that out!

@Peter: Glad to see you making good use of the tutorial!

gravatar
Yann
about a year ago

Yep, it's works great.

I actually made a lots of changes into that but override some function like the _login() one.
And i was able to add an activation email instead of adding the login role directly.

Just one info if someone need, if you want to load your module extending the Auth module, you have to load it in the bootstrap.php BEFORE the Auth module ! (take me a few hours to understand my mistake was there lol)

Thanks for all JDStraughan, can't wait to see your book !

gravatar
Ryan
about a year ago

Finally someone explains this stuff in a clear and concise manner. I was pulling hair I don't even have out! Works like a charm now. I look forward to the book, how about you send me an email when it comes out? ;)

gravatar
youngpac
about a year ago

thanks JDStraughan i saw ma mistake and rectified it.. your tutorial has gone a long way in helping me understand!

gravatar
Alex
about a year ago

Great example! Thanks alot!

gravatar
skyman
about a year ago

Great article man!

P.s. You should mention about Cookie::$salt beacuse it's important too :)

gravatar
Sune
about a year ago

Hello Straughan and other readers.

Nice blog you have here.

I am new to Kohana and followed your tutorial to create the login functionality.

However I can not figure out how to change the password requirements other than changing /modules/orm/classes/model/auth/user.php

Do I really have to change this file? I find that the Kohana style is overriding the default behavior with custom behavior, so I would find it odd having to change the ORM module files directly.

Thanks.

gravatar
Zetell
about a year ago

Thank you very much for this guide! It helped me upgade my Kohana-based project from 3.0.x branch to 3.1.

gravatar
Arthur
about a year ago

Thank you very much, for this article! Best Auth guide for kohana.

gravatar
Mondongo
less than a year ago

Thanks for doing this!

Just got your book in Kindle edition, it's what I was looking for!

gravatar
Scott
less than a year ago

Thank you for an amazing tutorial! This was my introduction to Kohana and it went swimmingly ;)

gravatar
jorie
a couple of months ago

Very nice tutorial! the only problem is that I'm stuck with an error after following it:

ErrorException [ Warning ]: array_fill(): Number of elements must be positive

can't really find anything about it on google, any ideas?