This is the 3rd part of my mini-series on Kohana 3 auth. I figure the best way to show the Kohana Auth module works is to provide a sample application module which uses auth. In this series of posts, I discuss:

  1. Setting up the basic Auth in KO3 (part 1)
  2. An overview of the functionality provided by the Auth module (part 2)
  3. Tips on implementing Auth in a custom application (part 3; this part)
  4. Getting started with Useradmin, my Kohana 3 auth admin module (part 4)

The new release is AWESOME :), and now is based on the Kohana 3.1.x API. (You can get the old version for 3.0.x via Github.)

Here are a few screenshots:

New UI, more providers supported! Get it from:

This is a custom module, and the code is in /modules/user. The code contains way more comments than usual. I've tried to make things clear, and keep the UI basic but pretty enough.

Further, the code is released under the BSD licence, so you can use it in commercial applications. While I don't restrict you in using the code, it is good karma to send improvements to this module back: in particular, I would like to have the features in the roadmap below.

What functionality is there:

  • Use the base Ko3 auth module and do not reinvent the wheel -- OK
  • Support translation; no strings without __() -- OK
  • Boilerplate for administration (create user, edit user, delete user, list users and last logins) -- OK; roles have to be added via DB but that's OK
  • Boilerplate for users (login, logout, view profile, edit profile, unregister, access denied page; error handling/validation) -- OK
  • reCAPTCHA optionally supported for registration -- OK; implemented for registration, thank you jnbdz!
  • Optional support for 3rd party login:

    • Facebook -- OK
    • Google -- OK
    • Yahoo -- OK
    • Twitter -- OK (with the caveat that Twitter does not allow you to get the email address via api)
  • Login using username, email or both -- OK
  • Optional lost password reset via email -- OK; workflow is a bit clunky (user has to type the new password one time)
  • Optional autologin support via cookies -- OK but not documented here; read about it Ko3 docs. Need to add checkbox to UI.
  • Optional enforcement of number of failed login attempts -- is there but configuration/activation is clunky.
  • UI design that doesn't make your eyes bleed -- OK
  • Kohana 3.1.x support - OK, thank you gartz!
What needs to be done - contribute this!

  • reCAPTCHA optionally supported for too many failed logins
  • Optional account activation via confirmation email + expire unactivated account/welcome message via email
  • Optional password strength indicator for client
  • Separation between module and sample app on Github -- e.g. migrate development to Github only
Nice-to-have (perhaps release as compatible modules?)

  • Profile picture loading from Facebook, Twitter or uploaded image -- not yet.
So if you implement any of those, and don't it to be too much of a burden to publish them under the BSD licence, let me know via comments or via email (firstname.surname@gmail.com).

Changelog is in the readme file on Github/Bitbucket.

Useful code snippets

Here are some code snippets which show you the basics of Auth:

Create a new user (e.g. if you have not set up any users yet and want to do that programmatically)

$model = ORM::factory('user');
$model->values(array(
   'username' => 'admin',
   'email' => 'admin@example.com',
   'password' => 'admin',
   'password_confirm' => 'admin',
));
$model->save();
// remember to add the login role AND the admin role
// add a role; add() executes the query immediately
$model->add('roles', ORM::factory('role')->where('name', '=', 'login')->find());
$model->add('roles', ORM::factory('role')->where('name', '=', 'admin')->find());

Check whether current user has a role

Auth::instance()->logged_in('admin') // for current user
<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; line-height: 19px; white-space: normal; font-size: 13px;">[/codesyntax]</span>

Check whether some other user has a role

// Load the role
$role = ORM::factory('role', array('name' =&gt; 'admin));
// Check that the user has the given role
$status = $user-&gt;has('roles', $role);

Add a role to a user

// add() executes the query immediately, and saves the data (unlike the KO2 docs say)
$user-&gt;add('roles', ORM::factory('role')-&gt;where('name', '=', 'admin')-&gt;find());

Remove a role from a user

// remove() executes the query immediately
$user-&gt;remove('roles', ORM::factory('role')-&gt;where('name', '=', 'admin')-&gt;find());

Retrieve all users that have a particular role

// find all the reviewer users in order of their username
$role = ORM::factory('role')-&gt;where('name', '=', 'reviewer')-&gt;find();
$users = $role-&gt;users-&gt;order_by('username', 'DESC')-&gt;find_all();
foreach($users as $user) {...}

Documentation on the interesting bits

The template view

The template view in /views/default/template.php uses the Thematic-inspired markup which I discuss in this post. You can do a lot by adding a few more divs into the header div. I have tried to follow my own advice about CSS, but the included CSS file is pretty basic. It includes the Yahoo CSS reset.

Controller_App: base controller class

This is the "base" controller I use for all the classes. It handles template autoloading and auth checks. You should inherit all your controllers from Controller_App to benefit from its features.

There is also one additional neat thing: in case of session expiry, Kohana may show an error in session loading. There is an additional check for this in Controller_App, which discards the session in case of loading errors.

Template autoloading loads the file from /views/default/template.php, so that you just have to set $this->template->content = $view->render() in your controller.

Auth checks are configured through two additional properties in the controller:

/
  Controls access for the whole controller, if not set to
FALSE we will only allow user roles specified.
 
  See Controller_App for how this implemented.
 
  Can be set to a string or an array, for example array('login', 'admin') or 'login'
 /
public $auth_required = FALSE;

/ Controls access for separate actions
 
   See Controller_App for how this implemented.
 
   Examples:
 * 'adminpanel' =&gt; 'admin' will only allow users with

Comments

Glenn Bennett: Wow...thanks so much. I have an application that's I'm using that I didn't write and the user management was missing and it in process of dinking around with the database I kinda messed thing up a bit.

Long story short(er) I found your article here and added domain management pretty easily by just installing your whole sample then hooking to the other database and with a few tweeks here and there I created a user management app for my application.

Thanks so much...great work.

P.S. Kohana drives me nuts.

Yuzhou Zhu: When I check out the source code, I cannot find the files related to this tutorial, only a welcome.php in the controller folder. Am I missing something here?

Mikito Takada: The auth implementation is a module, so the files are in the modules/user/ directory, not under app/.

This is useful because if you already have an app, you can just copy the modules/user/ folder and enable it in bootstrap.php (before loading the core Auth module). Also, you can extend the module using the Kohana 3 HMVC mechanism by redefining e.g. the user model in app/models/.

Peter Weil: Mikito, thanks so much for this module! One issue that I'm initially running into: when I go to edit a user to say, change the role,, I get this error:

"Invalid method validate_edit called in Model_User"

(this is the call from line 88 in Controller_Admin_User)

But this very method-- validate_edit() -- is right there in Model_User. I'm still trying to figure out why I'm getting this error.

Mikito Takada: Thanks for the feedback!

Editing user roles works using the unmodified repo. Do you happen to have a file named /application/classes/model/user.php?

Kohana's HMVC will use that file instead of /modules/user/classes/model/user.php if you do, so you need to copy the extra functions there - and associations... from the modules directory model when you start writing your own User model.

Peter Weil: The was another Model_User class -- in the auth module. I just found the answer in the Kohana forums: the user module has to be loaded before the auth module:

http://forum.kohanaframework.org/discussion/comment/41585/#Comment_41585

Thanks!

Robert Van Sant: Thanks so much for this explanation and code sample. Sometimes I feel Kohana is great and sometimes I feel like why isn't it easier like CodeIgniter. I definitely like that it's strict PHP5 coding, hence why I'm trying very hard to learn the nuances of the Kohana framework. I have a question in terms of templating and Ajax. What is the best route to handle this. Is it a matter of returning the rendered blocks as a response and using a replace JavaScript function to replace that HTML block? I've done this before with other projects.

Thanks in advance for any advice.

Kyuo: Thanks for this great tutorial! I'm new with user-management, but I see in another user-manager a "Session Manager" with a session database table, here I not understand to handling the sessions. Is this session-table not necessary?

In this time there is no data for "user is online" or "user is offline since ???" or a list of online users. Coming this in a next tutorial? :)

Mikito Takada: If you want to implement this, there are basically two additional things you need to do:

1: keep track of when the user was last active (e.g. on each page load, update a value somewhere in the database)

2: show users as online if they were active within the last x minutes (e.g. a page load occurred in the last 15 minutes)

Sessions do no need any tables, they are a core functionality of PHP, see http://www.php.net/manual/en/function.session-start.php and are not stored in the database.

You can keep track of sessions in a table if you want to show other people whether a user is online. However, my focus here is on Auth and user account management, so the I only keep track of the last login (you can see that in the admin interface).

If you want to track user activity, you need to write the additional tracking yourself.

Pedro Sland: I've looked into some of the possabilities myself and I have decided that the best is to use json_encode(array(--vars here--)); For another project I simply wrote a simple array to xml class instead.

If you are using a template controller you will need to do something there not to get the header and footer.

flurry: hey mikito,

thanks for this realy nice backend and tutorial. it runs verry well with my kohana 3.0.8 except the $user->reset_token function is missing. is this a known bug, or is this a configuration problem from myself? i will add an additional functionality for validating an email adress by the user after registration, when i have time again.

Mikito Takada: Hi,

Yes, this is a known problem. I will update the repository with new KO3.0.8 compatible code, the improved schema, plus some added niceties soon.

flurry: hey,

thanks for the reply. i'm curious about it.

Ralph: Am I missing something? I downloaded the source, but the 'application/classes/controller' folder is empty aside from a default welcome.php file. Where are all the controller class files?

Ralph: Oh wait, talking to myself here--I guess they are in modules/user/

michael: Hi, I read your tutorial, which I mainly understand( I think ), but there is one thing that I dont understand, (i am new to Kohana), I thought that you needed to load your module in bootsrap, but I have searched, and havent seen where you did it...

I want to develop a module myself so i was wondering...

Mikito Takada: Hi,

See https://bitbucket.org/mixu/useradmin/src/f13a6dc8386d/application/bootstrap.php line 80. The module is actually called "user" for now, which reminds me that I should change that to "useradmin" next time I publish an update..

michael: I just found it, but i was going crazy. I thought i didnt understand.

It just "appeared" because I thought that it was added at the end of the modules list... But thanks for you fast answer!

michael: I have another question thought, Why do we need to load the user module before the auth module?

I tried to do a module on my own and "discovered" that it automatically hashed the password...

Mikito Takada: Module loading order matters because the useradmin module is extending Model_Auth_User.

Otherwise, Kohana will use the barebones user model from:

modules/auth/classes/model/user.php

instead of:

modules/user/classes/model/user.php

If you prefer, you can just copy modules/user/classes/model/user.php to application/classes/model/user.php; then the loading order won't matter.

Full explanation: http://kohanaframework.org/guide/about.filesystem and http://kohanaframework.org/guide/about.flow

Jason: Oh my god! THANK YOU, I am getting into Kohana and well the most buggery thing is the auth and user controls with roles :D so you have just saved me a arm and a leg and allow me to get knowing kohana from the heart of what i require todo :D

10/10

anton: Hello Mikito! I learned your class Model_Useradmin_User and there's area that i can't understand. In method login() you call: if ($this->loaded() AND Auth::instance()->login($this, $array['password'])) {.... but Auth::login(...){ ... $this->_login($username, $password, $remember); ... } and _login is abstract method which have to be defined for user. I tried to find subclass Auth where this and other two abstract method have to be defined but i couldn't find it. Please help my investigate it.

Mikito Takada: Hi Anton,

I am using the KO3 core Auth class. And as you can see in /modules/auth/classes /kohana/auth.php function instance(), the actual class is determined by configuration.

Auth can be configured at application/config/auth.php to either use the ORM driver or file driver. The default, and the one I test with is the ORM driver.

The concrete code for login() that is located in /modules/auth/classes/ kohana/auth/orm.php . Note that this is all KO3 core code.

anton: Ok. I find it. Thank y for halp.

anton: I have some question about your model. Why did y not write logged_in method and other like Kohana_Auth? Or do y think it's bad idea and need use Auth::logged_in method in controllers?

anton: Is it good idea write wrapper Kohana_Auth_Orm::logged_in() method?

Mikito Takada: Hi,

not sure I understand your question. I'm not writing any wrappers for Auth, because I am not trying to replace the core Auth functionality - I just want to build a module that uses KO3 Core Auth and adds the most commonly used functionality. So I only use/inherit from Auth, and extend the base Auth_User model a little bit in my User model to provide password resets and validation for changing the password after registering.

Why should there be a wrapper for Auth::logged_in? What would be the benefit?

jim: Hey, nice job you did there, Mikito! Are you going to update the module to work with the newest kohana version (3.1.1)? It would be great!

Mikito Takada: I am going to release a rather big and awesome update soon with Twitter, Google and Yahoo support as well as a new UI, it'll be out very soon (a couple of days?).

I will update to 3.1.x at some point, but not immediately as I am swamped with other work and the 3.0.x branch is still working perfectly fine (and is still supported). Looking at http://forum.kohanaframework.org/discussion/7793/kohana-3.0-to-3.1-upgrade-troubleshooting there are quite a few things to test/debug etc. The main issue is that KO 3.0.x Auth hashes aren't supported on 3.1.x and I don't have the time to figure out the migration right now.

Please give it a try yourself if you need to have KO 3.1.x support quickly.

jim: Can't wait :) Hope you get it done quick.

anang: Hi, Finally I can make your module works for Ko3.1. I'm not a good coder, and my kohana exprience is minimal, so I don't know whether my modification is the proper one.

The modification is 1-4 lines per function, except for action_profile_edit, I made a lot of changes.

not sure where to put my code, just ask me I'll email to who interested.

Mikito Takada: Hi Anang - send me the code, I'll merge and publish it on Github. My email is in the form: (firstname).(surname)@gmail.com...

Joe Dwyer: Mikito, thanks for building and sharing such a great module!

At first I ran into an issue because the driver (stupid me) was set to "File" and needed to be set to "ORM". That was easy to fix.

Then, it expected the pagination module, which was also pretty easy to add.

Now I'm getting an error saying that "The user_identity property does not exist in the Model_User class". It looks like that's related to the outside user identities (Facebook, Google, etc.). Is there a swing table it's looking for or something? Anything else I need to do to set that up?

Thanks in advance for the help!

Mikito Takada: Great!

You are probably loading the modules in the wrong order - so the Auth module Model_User is being used instead of the Useradmin module Model_User. The Auth version of Model_User does not have the additional relationships. Recheck your module loading order in bootstrap.php. More info in the "Getting Started with Useradmin" post: http://blog.mixu.net/2011/01/13/getting-started-with-useradmin-my-kohana-3-auth-admin-module/

Also, make sure you have the database structure from the SQL file in the module directory (foreign key constraints are not important if it complains about those).

Reading about your troubles reminds me that I should offer a "ready to go" version as well. There used to be one, but heavy Git/Github users prefer pure modules over full applications...

Joe Dwyer: Mikito! You are awesome! Thanks for the quick response. Looks like it's working now.

I understand users wanting pure modules, but I agree it's nice to have a fully "ready to go" version for people who are learning Kohana. It's much easier to throw something up, see that it works, and then start tweaking. When the tweaks break something... it's pretty easy to figure out what happened. But when you can't get it to work in the first place, it's much harder to figure out where the error is. After I get more used to Kohana, I suspect that won't be as much of an issue, though...

Have a great weekend!

John Rushworth: You have my undying love and gratitude. This is awesome!!! Exactly what was needed at exactly the right time. Thank-you very much for a GREAT job, stunningly executed. I had a few minor issues... not sure if they are typical or not... like user.last_failed_login and user.modified needing default values.... but this was as painless as it comes... and it does EXACTLY what it said on the box. Thanks again. Many karmic hugs.

anon: I have done everything described above but I am getting 404 error when I want to see x/user/login. 'x' is my base url. I cant understand why my default route does not catch the uri. I have declared the user module before the auth module. My default route is: ... Route::set('x/user', 'user(/(/))') ->defaults(array( 'controller' => 'user', 'action' => 'index', 'id' => NULL, )); Route::set('default', '((/(/)))') ->defaults(array( 'controller' => 'main', 'action' => 'index', ));

Guidouil: Great Job, using it for my project and loving it :D There is jeust a little challenge regarding the auto_login part with a cookie, as you say the kohana auth module handle it very well but there is a little problem with your database. The token field in the user_tokens table should be a varchar(40) and not 32. It now works better for me ^^ Thank you very much once again, keep up enhancing the stuff, I'm planning to fork it sometime ;)

Craig: Hi I'm trying to find the folder /modules/user in the github repository as mentioned in the post but I cannot seem to see this folder.

Can anyone give me any pointers where this module is?

Thanks in advance

Daniel Iser: Here's the way I worked out for apps with heavy Ajax usage. Rather than having to duplicate a lot of actions I set my main controller to detect if it was an Ajax request. If it was set the template to an json encoded output.

In my actions I prepare all values into the same variables for each $values, $errors, $messages etc.

The Json view combines $values, $errors and $messages into a single array and runs it through json_encode and prints it.

Doing it this way you have less code to write and it's easier to maintain when you have to make changes.

Anuruddha: Hi, I am a newbie to Kohana, and was trying to setup this as my starting point. I've downloaded the code from github, but wasn't successful in getting this running in my local. Is there any documentation about how to set this up? Or would you be able to give me the steps required for setting the code? Thanks A LOT in advance!

Casper: @Guidouil, thank you so much for your comment.

iPoul: Hi, i was wondering if its possible to use the Auth Module without the use of roles ? or am i bound to use them, unless i want to rewrite the module or make my own ?

Kind regards, iPoul

Bojan: Hi, I'm having the same problem as Joe Dwyer, but my bootstrap is fine I think.

The user_identity property does not exist in the Model_User class

The problem is when I use pagination (admin_user).

Kohana::modules(array( 'useradmin' => MODPATH . 'useradmin', // Mixu's useradmin module 'auth' => MODPATH . 'auth', // Basic authentication multiple backends 'database' => MODPATH . 'database', // Database access 'orm' => MODPATH . 'orm', // Object Relationship Mapping 'pagination' => MODPATH . 'pagination', // pagination ));

"Ready to go" would be awesome!