obAuth Component Tutorial

By Steve Oliveira (coeus)
This is a tutorial on how to use obAuth component: http://bakery.cakephp.org/articles/view/130
This is a simple authentication component that I wrote as a quick way to protect areas of your web application. I wanted manual control of what permissions are granted to what action. Something as simple as this:
Download code <?php $this->obAuth->lock(array(1)); ?>

At the top of my action should mean only users belonging to usergroup 1 are allowed to use this action.



Before we begin this tutorial, it's important to note that this component is in it's alpha stages. I would love feedback from some of the more experienced bakers out there. Enjoy



1. Download the component
2. Recommended database tables
3. Include the component and create models
4. Required actions
5. Start securing our private areas!
6. Extra Tips & Tricks



1. Download the component



Get the component here: http://bakery.cakephp.org/articles/view/130



2. Recommended database tables



Essentially you'll want a users table and a groups table. The users table will hold the regular user data and a foreign key called "group_id". The groups table is where you store your groups. Your tables should look like the following:



Download code
-- --------------------------------------------------------

-- 
-- Table structure for table `groups`
-- 

CREATE TABLE `groups` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `name` varchar(50) NOT NULL default '',
  `created` datetime NOT NULL default '0000-00-00 00:00:00',
  `modified` datetime NOT NULL default '0000-00-00 00:00:00',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT;

-- 
-- Dumping data for table `groups`
-- 

INSERT INTO `groups` (`id`, `name`, `created`, `modified`) VALUES (1, 'Member', '0000-00-00 00:00:00', '0000-00-00 00:00:00');
INSERT INTO `groups` (`id`, `name`, `created`, `modified`) VALUES (2, 'Admin', '0000-00-00 00:00:00', '0000-00-00 00:00:00');

-- --------------------------------------------------------

-- 
-- Table structure for table `users`
-- 

CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `username` varchar(50) NOT NULL default '',
  `password` varchar(32) NOT NULL default '',
  `fname` varchar(50) NOT NULL,
  `lname` varchar(50) NOT NULL,
  `email` varchar(100) NOT NULL default '',
  `group_id` int(10) unsigned NOT NULL default '0',
  `active` tinyint(1) unsigned NOT NULL default '0',
  `created` datetime NOT NULL default '0000-00-00 00:00:00',
  `modified` datetime NOT NULL default '0000-00-00 00:00:00',
  PRIMARY KEY  (`id`),
  KEY `group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT;


You can do whatever you want with your users table as long as the fields id, group_id, username, password and active are there



3. Include the component and create models



Ok, this is relatively easy. Once you've downloaded the component you need to place it in your /app/controllers/components directory. Once you've placed it in the right directory you need to call it in your controller file. So let's use posts_controller.php as an example:



Model Class:

Download code <?php 
class PostsController extends AppController 
{
    var 
$name 'Posts';
    var 
$components = array("obAuth");
}
?>


Next you should have a User model and a Group model with the proper associations. Users belong to a group and groups has many users. Please refer to this tutorial if you don't understand how to setup associations go here: http://wiki.cakephp.org/docs:understanding_associations



That's it. You can now start using the obAuth component.

4. Required actions



To begin using the obAuth component we need to have to important actions. "Login" and "Logout". They are self-explanatory, so I don't need to tell you what they're for. Instead I'll show you how mine look:



In /app/controllers/users_controller.php

Download code
<?php
function login()
{
    if(isset(
$this->data['User']))
    {
        if(
$this->obAuth->login($this->data['User']))
        {
            
$this->redirect('/users');
        }
        
$this->flash("Username/Password is incorrect");
    }
}

function 
logout()
{
    
$this->obAuth->lock();
    
$this->obAuth->logout();
    
$this->flash('You are now logged out.');
    
$this->redirect('/');
}
?>


Notice I use a method obAuth::login($data). This method is used to check the data posted with a user in the database. If one exists then the user will be authenticated



Now for the view you really only need one for the login action because logout just redirects. So the view should look something like this



View Template:

Download code
<?php echo $html->formTag('/users/login')?>
<fieldset>
    <legend>User Login</legend>
    
        <label for="username">Username: </label>
        <?php echo $html->input('User/username', array('style' => 'width: 150px'))?><br />
    
        <label for="password">Password: </label>
        <?php echo $html->password('User/password', array('style' => 'width: 150px'))?><br />
    
        <label for="submit"> </label><br />
        <?php echo $html->submit('Sign In')?>    
</fieldset>
</form>


5. Start securing your private areas



Alright, now that we have our login and logout actions we can get users authenticated. So let's start securing our actions by setting the permissions. Let's say we have an action in our Posts controller called "add" and we only want users from the group id "3" allowed to use it. Well this is all we need to do to secure the action



Download code
<?php
function add()
{
    
$this->obAuth->lock(array(3)); // Only users with the group_id '3' are allowed here

    
if (!empty($this->data))
    {
        if (
$this->Post->save($this->data))
        {
            
$this->flash('Your post has been saved.','/posts');
        }
    }
}
?>


Now notice that 1 line of code used to secure this action. It uses obAuth::lock($groups=null, $redirect=null). The array passed is an array of group ids allowed to access this action. So if you had Super Administrators (3) and Administrators (2) and wanted them to have access to this action you would write:


Download code $this->obAuth->lock(array(2,3));.

If you leave $groups parameter empty then it'll allow any AUTHENTICATED user to access the action. So you're still securing it from anonymous visitors. The $redirect parameter allows you set a url that will redirect a user who's not allowed to access the action. If you leave it empty it will redirect the user to the login page.

 

Comments 121

CakePHP Team Comments Author Comments
 

Question

1 associations

Can you elaborate on the associations part?
Posted Nov 5, 2006 by zomg
 

Comment

2 Associations

Associations are explained in the link given: http://wiki.cakephp.org/docs:understanding_associations

You need to create associations between user and group - or get Bake.php to do it for you.
Posted Nov 10, 2006 by Marcus Bointon
 

Question

3 Lock() inside views

How should I call lock() inside a view? For example, in an index view I might want to only show an add link to some users, but everything else should remain the same.
Posted Nov 10, 2006 by Marcus Bointon
 

Comment

4 Lock() inside views

How should I call lock() inside a view? For example, in an index view I might want to only show an add link to some users, but everything else should remain the same.

Lock is more for preventing actions from being run from unauthorized user/groups.

If a user has authorization to use an action but shouldn't see certain links, you'll have to put if statements in your view where user ID and group ID are the conditions in it to only show the link to authorized users.

Posted Nov 12, 2006 by Steve Oliveira
 

Bug

5 user is seen as array not object

First, thanks for this. It is a real help and it has helped me out a lot in understanding CakePHP!

When I tried this tutorial out, it worked, but I got the Notice:

Notice: Trying to get property of non-object in C:\web\aclusc-leg\app\controllers\components\ob_auth.php on line 73

Line 73 was:

$usergroup = $this->user->usergroup;

Its seems as if the line in obAuth::startup()

$this->user = $this->Session->read($this->sesskey);

creates an array rather than an object. So in my test, $this->user->usergroup does not exist, but I could get a usergroup id by using:

$usergroup = $this->user['Group']['id'];

Is there something I'm missing? Should $this->user be an object with the property usergroup?
Posted Nov 16, 2006 by Aran Johnson
 

Bug

6 Bug

As Aram experienced, there's a bug in the obAuth code.

You can replace:

$usergroup = $this->user->usergroup;

With:

$usergroup = $this->user['Group']['id']; // fix

Or why not use the "function getGroupId()" in the code?

$usergroup = $this->getGroupId(); // fox
Posted Nov 20, 2006 by Kim Biesbjerg
 

Comment

7 Bug fix

Thanks, I've spotted the bug and just removed that line altogether because it wasn't being used.

Nate:

Yes, that's a good way of doing it as well and I may change it to that way. As mentioned this is still in alpha stages, haven't had time recently to tweak it, but will be doing so soon.

Ultimately I want the option for it to be controlled manually (Put in the permissions right in the code) or automatically (handle permissions through admin interface and storing them in DB)
Posted Nov 22, 2006 by Steve Oliveira
 

Question

8 where is obauth

hi steve
i've been recommended to use obauth by a friend of mine.

the link to http://bakery.cakephp.org/articles/view/130 is inactive. has this auth method evolved to something better or is it gone for good?
Posted Nov 27, 2006 by mike karthauser
 

Comment

9 obauth back

thanks for getting the obauth link back up.
Posted Nov 28, 2006 by mike karthauser
 

Comment

10 Tutorial should mention that the password stored in the Db should be hashed...

according to the login_hash in the login function of the component.
Posted Dec 31, 1969 by Tazz
 

Question

11 using lock() in beforeFilter()

Does anyone know of a way to use $this->obAuth->lock() in the controller beforeFilter?
Posted Dec 31, 1969 by Nate Constant
 

Comment

12 Helper

It's small helper (sorry, I wrote them at 1:30 am ;-)
And suddenly Session->valid - doesn't work

But in view you can get TRUE, or FALSE and combine your view elements for admin/members/etc.

class obAuthHelper extends Helper {

var $helpers = array (
'Session'
);

var $sesskey = "mYpERsOnALhaSHkeY";

// Check is user is part of usergroup specified
function lock($groups=null)
{

$hasAccess = false;

if ($this->Session->check($this->sesskey))
{
$this->user = $this->Session->read($this->sesskey);
}

if (!empty($this->user))
{
if (!empty($groups))
{
foreach ($groups as $group)
{

if ($this->user["Group"]['id'] == $group || $this->user["Group"]['name'] == $group)
$hasAccess = true;
}
}
else
{
$hasAccess = true;
}
}



if ($hasAccess) return 1;
else return 0;

}

}
Posted Apr 10, 2007 by Vladislav
 

Comment

13 RE using lock() in beforeFilter()

Does anyone know of a way to use $this->obAuth->lock() in the controller beforeFilter?

I use something like this

function beforeFilter(){
$this->obAuth->startup($this);
if(isset($this->params['admin']))
{
$this->obAuth->lock(array(1,2));
}
Posted Jun 23, 2007 by Ksu Zhytomirsky
 

Comment

14 Getting this error...

I am getting the following error when trying to implement this component:

Warning: session_start() [function.session-start]: Cannot send session cache limiter - headers already sent (output started at /home/6813/domains/cheesecake.convandesign.com/html/app/controllers/components/ob_auth.php:118) in /home/6813/domains/cheesecake.convandesign.com/html/cake/libs/session.php on line 146

Warning: Cannot modify header information - headers already sent by (output started at /home/6813/domains/cheesecake.convandesign.com/html/app/controllers/components/ob_auth.php:118) in /home/6813/domains/cheesecake.convandesign.com/html/cake/libs/session.php on line 147
Posted Jul 17, 2007 by Conrad VannLandingham
 

Comment

15 Getting this error...

Make sure you don't have any spaces before or after the <?php ?> tags.
Posted Aug 4, 2007 by Steve Oliveira
 

Question

16 Error msg.

Hi,

I receive the following error, while literally following yout tutorial:


Notice: Trying to get property of non-object in /var/www/vhosts/iamin.com/subdomains/cms/httpdocs/app/controllers/components/ob_auth.php on line 72

Notice: Trying to get property of non-object in /var/www/vhosts/iamin.com/subdomains/cms/httpdocs/app/controllers/components/ob_auth.php on line 74

Fatal error: Call to a member function redirect() on a non-object in /var/www/vhosts/iamin.com/subdomains/cms/httpdocs/app/controllers/components/ob_auth.php on line 96
Posted Sep 14, 2007 by Bo
 

Bug

17 Authentication gone wrong

I think there is a mistake in your Authentication component, at least, it seems that implementing it the way you say does not seem to be secure, let me explain the case.


When calling an action, it seems that although the lock method tries to redirect the browser (to the deny page) the controller does finish the remaning code before the actual redirection takes place!!!

This means that calling for instance http://domain.com/blog/delete/3

Does not require me to be logged in to delete the blog with id=3 although the first line of code in this action is:

$this->obAuth->lock(array(1));

.........

Ofcourse you are able to fix this problem by letting the lock method return true/false and using an if statement in your action to determine the access of the user

function action() {
if($this->obAuth->lock(array(1))) {
do something securely!
}
}

Also you have to add: "return $hasAccess;" just before the closing } of the lock method inside the obAuth component.
Posted Sep 14, 2007 by Bo
 

Comment

18 Update to bug report

The problem could also be fixed by adding exit(); after the redirect in the lock method, this keeps the controller action from finishing the remaining lines of code! So this means only the line needs to be called without the if() statement.

Could you please fix this is the component on the linked page, because this can cause serious problems, since it is easily missed!
Posted Sep 14, 2007 by Bo
 

Comment

19 Get errors...

Hi, I receive exactly the same error as Bo...
in the lines i have:
72: if ($this->controller->action != "login")
74: $this->last_page = $this->controller->here;
96: $this->controller->redirect($page);
Might Ob or someone else help me here??

Hi,

I receive the following error, while literally following yout tutorial:


Notice: Trying to get property of non-object in /var/www/vhosts/iamin.com/subdomains/cms/httpdocs/app/controllers/components/ob_auth.php on line 72

Notice: Trying to get property of non-object in /var/www/vhosts/iamin.com/subdomains/cms/httpdocs/app/controllers/components/ob_auth.php on line 74

Fatal error: Call to a member function redirect() on a non-object in /var/www/vhosts/iamin.com/subdomains/cms/httpdocs/app/controllers/components/ob_auth.php on line 96
Posted Oct 23, 2007 by Jacob
 

Question

20 User in more than one group

It says that "Users belong to a Group" but why not make it so that Users to Groups is a HABTM relationship? Or, more succinctly, my question is

Is it possible to use this auth component with Users -> HABTM -> Groups so that a User can belong to more than one group?

It seems like authentication would be more flexible this way.

Or is there an existing authentication component that does this?


Thanks. Later...
Posted Oct 23, 2007 by Jeffrey Silverman
 

Comment

21 Major Security Issue

Bo is correct, there is a huge security hole here. I found this out the hard way with a client calling up saying their data was gone!

YES it sucked BIG time. However it was a very easy fix (thanks to Bo's advice) and simply putting an exit(); after the redirect in the lock method of ob_auth.php (this is after it does the whole redirect if you don't have access, before that if statement closes..so the if(!$hasAccess)...part) solves the issue quite nicely.

If someone directly puts in www.com/controller/delete/5 for example, it will direct them to log in and all will be fine.

This is a MAJOR security issue that I'm glad it was simple to fix.
Posted Nov 5, 2007 by Tom Maiaroto
 

Comment

22 Not working on Cake 1.1.18

This component are not working in ver 1.1.18, probably because they change the way sessions are handled.

Anybody know how to fix this?
Posted Nov 5, 2007 by Aria Rajasa
 

Comment

23 Cake 1.18

Is there a solution to fix this for 1.18, I was just considering upgrading until I read this post. I'm using this component for security, so it would be very unfortunate if this component stops working.
Posted Nov 15, 2007 by Bo
 

Bug

24 flash vs. setFlash

replace all $this->flash with $this->Session->setFlash if you're getting errors. I'm using CakePHP 1.1.18.
Posted Nov 17, 2007 by James Revillini
 

Comment

25 Working like a charm

thx james! work like a charm
Posted Nov 17, 2007 by Aria Rajasa
 

Comment

26 Add Ajax Request Support

...
var $components = array('Session','RequestHandler');
...
if(!$hasAccess)
{

if (!$this->RequestHandler->isAjax()){
$page = (!empty($redirect)) ? $redirect : $this->login_page;
$this->controller->redirect($page);
} else {
$this->controller->render('..'.$this->login_page,'ajax');
}

}
...
that`s all.
Posted Dec 17, 2007 by Alexander
 

Comment

27 Does not stay logged in

I can't get the component to work correctly. For some reason I can't get the authentication to stay logged in. When I login in it takes me to the correct default page, but when I try to navigate anywhere else or refresh the same default page I am redirected to login as if I was never logged in.

Any ideas?
Posted Jan 2, 2008 by Chris
 

Bug

28 Doesnt work for me v1point2stuff RCIsomething

Warning: array_merge() [function.array-merge]: Argument #2 is not an array in /home2/zendscom/public_html/cake/cake/libs/controller/component.php on line 58

Warning: Invalid argument supplied for foreach() in /home2/zendscom/public_html/cake/cake/libs/controller/component.php on line 85
CakePHP Rapid Development
Missing Model

No class found for the Login model

Notice: If you want to customize this error message, create app/views/errors/missing_model.thtml.

Fatal: Create the class below in file : app/models/login.php

<?php
class Login extends AppModel {
var $name = 'Login';
}


When I try to access login. I'm new to Cake, but users.php & login.php both are not "found" as models for me.
Posted Jun 9, 2008 by mike floering
 

Comment

29 RE Doesnt work for me v1point2stuff RCIsomething

Warning: array_merge() [function.array-merge]: Argument #2 is not an array in /home2/zendscom/public_html/cake/cake/libs/controller/component.php on line 58

Warning: Invalid argument supplied for foreach() in /home2/zendscom/public_html/cake/cake/libs/controller/component.php on line 85
CakePHP Rapid Development
Missing Model

No class found for the Login model

Notice: If you want to customize this error message, create app/views/errors/missing_model.thtml.

Fatal: Create the class below in file : app/models/login.php

<?php
class Login extends AppModel {
var $name = 'Login';
}


When I try to access login. I'm new to Cake, but users.php & login.php both are not "found" as models for me.


You definetely should have a Users model in /app/models/user.php and also a Group model in /app/models/group.php Inside the User's controller (not the model, this should be saved in /app/controllers/users_controller.php) you should include the login() and logout() methods with the following code:


<?php
function login()
{
    if(isset(
$this->data['User']))
    {
        if(
$this->obAuth->login($this->data['User']))
        {
            
$this->redirect('/users');
        }
        
$this->flash("Username/Password is incorrect"); // $this->Session->setFlash("Username/Password is incorrect"); for newer versions of the framework
    
}
}

function 
logout()
{
    
$this->obAuth->lock();
    
$this->obAuth->logout();
    
$this->flash('You are now logged out.'); // $this->Session->setFlash('You are now logged out.'); for newer versions of the framework
    
$this->redirect('/');
}
?> 


Then try again and see if it works :)
Posted Jun 25, 2008 by Javier Constanzo