DarkAuth v1.3 - an alternative Auth

3 : Setup Steps (5 little ones)

By Chris Walker (theChrisWalker)
An update to the original DarkAuth component (http://bakery.cakephp.org/articles/view/darkauth-another-way), with increased efficiency and easier access control. It works exactly how I want to, so it might not be your first choice, but it's a solid alternative to the inbuilt AuthComponent.

Main Features:

- Per action / per controller / inline access control
- Group support for HABTM and BelongsTo Group associations, or disable groups control completely.
- A "super-user" functionality allowing one group automatic access granted
- Optional tamper-proof Cookie support
- Custom password hashing to suit your Model
- Passes User Info and Access Matrix to view
- Methods to access User Info / Access Matrix in Controller
The following steps should guide you through the setup process and the files you need to alter.

Of course, you will need to have the models for your User table (and groups if applicable). I will assume you have these models setup with Cake conventions with the following schema (using HABTM association):


CREATE TABLE `users` (
`id` int(11) NOT NULL auto_increment,
`created` datetime default NULL,
`modified` datetime default NULL,
`live` tinyint(1) NOT NULL default 0,
`username` varchar(16) NOT NULL default '',
`pswd` varchar(32) NOT NULL default '',
PRIMARY KEY (`id`)
);

CREATE TABLE `groups` (
`id` int(11) NOT NULL auto_increment,
`created` datetime default NULL,
`modified` datetime default NULL,
`live` tinyint(1) NOT NULL default 0,
`name` varchar(32) NOT NULL default '',
PRIMARY KEY (`id`)
);

CREATE TABLE `groups_users` (
`group_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
KEY `group_id` (`group_id`,`user_id`)
);

If you don't use the HABTM association, then remember to set var HABTM = false; later. This will then assume that the user $belongsTo a group (and therefore you'd need a "group_id" field in your "users" table).

Look at the Cake Manual for how to setup the Models for these tables.

Step 1: AppController


If you have created an AppController in your own controllers directory, nows the time, create a file called app_controller.php and populate it as follows. If you have got one, it should be easy enough to see what you'll need to add to yours.

Controller Class:

<?php 
class AppController extends Controller {
  var 
$uses = array('User');
  var 
$components = array('DarkAuth');

  function 
_login(){
    if(
is_array($this->data) && array_key_exists('DarkAuth',$this->data) ){ 
      
$this->DarkAuth->authenticate_from_post($this->data['DarkAuth']);
      
$this->data['DarkAuth']['password'] = '';
    }
  }
  
  function 
logout(){
    
$this->DarkAuth->logout();
  }
}
?>

Step 2: Login and Deny Views


You can create these however you want, however I discovered something very useful in that you can render Views using Controller::render() using absolute paths, so Controller::render('/login') would render a view in the root of your Views Folder. Using this to our advantage we can allow an arbitrary controller access to a view via the same render path. So I create a login View at /app/views/login.ctp, again it's up to you but it must post the following data:

[DarkAuth][username],
[DarkAuth][password]
and optionally if you have set the "$allow_cookie" variable:

[DarkAuth][remember_me],
[DarkAuth][cookie_expires],

Here's a simple one which will do the trick:

View Template:


<?php
  $this
->pageTitle 'Access Restricted';
  echo 
$form->create('DarkAuth',array('url'=>substr($this->here,strlen($this->base))));
  echo 
$form->input('DarkAuth.username');
  echo 
$form->password('DarkAuth.password');
                
/* Uncomment for cookies...
  echo $form->input('DarkAuth.remember_me',array(
          'label'=>'Remember Me? (uses cookies)',
          'type'=>'checkbox'
          ));
  echo $form->input('DarkAuth.cookie_expiry',array(
          'options'=>array(
                       'now'=>'end of session',
                       '+1 week'=>'in a week',
                       '+1 Months'=>'in a month',
                       '+6 Months'=>'in 6 months',
                     ),
          'label'=>'If so, for how long?'
          ));
*/
  
echo $form->end('login');
?>

And a page for /app/views/deny.ctp:

View Template:


<?php
  $this
->pageTitle 'Access Denied!';
?>
  <p>I'm sorry, but you don't have sufficient permission to access this page!</p>

Step 4: Edit the Component's Variables and Hasher


There are a number which need to be configured to match your user and group models, the fields they use for username and password and the association type.

There are others for successful logout, login failure messages, default redirections and more. Please look over them to get the component to work how you want it.

The final thing to configure is the DarkAuth::hasher() function (which can be used anywhere to hash passwords in the same way that they are hashed in the database. Make sure your either use the same hashing function or amend this one how you want.

Step 5: The Logout Route


This is optional, as we put the logout() function in AppController so accessible from any controller. However, I find it more aesthetically pleasing to have a route for logout at /logout. Add this to your app/config/routes.php:


Router::connect('/logout',array('controller'=>'users','action'=>'logout'));

NB any controller would do, but you're pretty sure to have a UsersController.

Step 6: Enjoy!


That's it. Hopefully you haven't had too many issues, and your App is now secure and happy.

Comments 670

CakePHP Team Comments Author Comments
 

Bug

1 Variable variable needed at line 308ish

Line 308 should be:

      // single group assoc, id = user.group_id
      $foreign_key = $this->controller->{$this->user_model_name}->belongsTo[$this->group_model_name]['foreignKey'];

PS: great job, btw! I use this component in all of my Cakes!
Posted May 6, 2008 by Scott Everson
 

Bug

2 Lillte Bug on the tutorial

In the tutorial you tell us to use
var $_DarkAuth = array('requires'=>array('Admin','SecretKeepers'));

When actually we have to use 'required' insted of 'requires'.

Just this litlle thing ....
You component rlz :)
Posted May 7, 2008 by Bruno Lima
 

Question

3 Not quite working...

I fear this may be something simple because no one else seems to be having this problem, but I am currently stumped, so here goes...

I've dropped in the code and made the indicated modifications to my code. Now, when viewing a page I get a fatal error on this line of the _generateAccessList() function:

$all_groups = $this->controller->{$this->user_model_name}->{$this->group_model_name}->find('list');
Doing what debugging I could, there doesn't seem to be any Group in User, so you can't run the find() method on the invalid Group object. I don't know if it's relevant that the user isn't logged in.

I would certainly appreciate any help or suggestions.
Posted May 8, 2008 by Sam Brown
 

Comment

4 Some Replies

Thank you all for the support, I have changed the code as suggested by Scott, and the bits of the tutorial noticed by Bruno.

Doing what debugging I could, there doesn't seem to be any Group in User, so you can't run the find() method on the invalid Group object. I don't know if it's relevant that the user isn't logged in.

I would certainly appreciate any help or suggestions.

I think the issue is that you don't have a Group model associated with your User Model. If that is the case, you'll need to set the $group_model_name variable to false, or an empty string.

This will tell the component you are not using groups. I will try and make that clearer in the comments.
Posted May 11, 2008 by Chris Walker
 

Question

5 DarkAuth and output buffering

I have to live with output_buffering being set to off on my server. I believe that that is why using DarkAuth doesn't work for me. After I enter my credentials into the login form I get Cannot modify header information - headers already sent by (output started at /opt/lampp/htdocs/at/controllers/components/dark_auth.php:383)
It seems to me that this is because DarkAuth writes to the output before the page loads.

Has anyone got it working with "output_buffering = Off"?
Posted Jun 12, 2008 by Stephan
 

Comment

6 Problem with your source file...

I have to live with output_buffering being set to off on my server. I believe that that is why using DarkAuth doesn't work for me. After I enter my credentials into the login form I get Cannot modify header information - headers already sent by (output started at /opt/lampp/htdocs/at/controllers/components/dark_auth.php:383)
It seems to me that this is because DarkAuth writes to the output before the page loads.

Has anyone got it working with "output_buffering = Off"?

Have you checked what is on line 383? My file ends at line 355. If you have any white space characters outside of the then that will be sent to the browser, and with no output buffering, headers for cookies can't be written and this warning will be produced. I would check your file for whitespace..
Posted Jun 12, 2008 by Scott Everson
 

Comment

7 RE DarkAuth and output buffering

@Stephan, Scott is probably spot on and thats the first thing I would suggest.

@Scott, Thanks, that's almost certinaly the cause.
Posted Jun 13, 2008 by Chris Walker
 

Bug

8 DarkAuth and RC1

This code doesn't quite work with RC1, only a little glitch and easy to fix.

Simply replace the 2 instances of
$this->controller->render to
echo $this->controller->render
Posted Jun 13, 2008 by Chris Walker
 

Question

9 User view your self profile.

Hi Chris,
How i can allow the user only view your self profile, in my controller?
Ex. If user with id 5 try to access www.site.com/users/view/1 ou www.site.com/users/edit/1 this user receive one error message.
Thank's for a great component and tutorial
Posted Jun 15, 2008 by Bill
 

Comment

10 RE User view your self profile

Hi Chris,
How i can allow the user only view your self profile, in my controller?
Ex. If user with id 5 try to access www.site.com/users/view/1 ou www.site.com/users/edit/1 this user receive one error message.
Thank's for a great component and tutorial

Controller Class:

<?php function edit($id){
  
$user $this->DarkAuth->getUserInfo();
  if(
$user['id'] == $id){
    
// allow
  
}else{
    
// error message.
  
}
}
?>

Nothing in this component to do that automagically, but I have implemented it in this way many times... good luck!
Posted Jun 15, 2008 by Chris Walker
 

Comment

11 Cannot make it work

Hi, I think your component is very useful and I wanted to thank you for your time and effort. I followed your tutorial here but can't make it work. I get the following error with DDD:

Fatal error: Maximum function nesting level of '100' reached, aborting! in /var/www/tandil/cake/libs/debugger.php on line 143

I think that when the app is trying to load the component it crashes, but I don't know how to fix this. Any ideas? I'm using the 1.2.0.7125 RC1. I can also post you the Call Stack if you need it.

Thanks in advance,

Javier.-
Posted Jun 24, 2008 by Javier Constanzo
 

Comment

12 RE Cannot make it work

Hi, I think your component is very useful and I wanted to thank you for your time and effort. I followed your tutorial here but can't make it work. I get the following error with DDD:

Fatal error: Maximum function nesting level of '100' reached, aborting! in /var/www/tandil/cake/libs/debugger.php on line 143

I think that when the app is trying to load the component it crashes, but I don't know how to fix this. Any ideas? I'm using the 1.2.0.7125 RC1. I can also post you the Call Stack if you need it.

Not come across this one before, do send me all the details though and I'll have a look. Some people have reported problems with IIS, so would be interested in you server setup too.

http://thechriswalker.net/contact
Posted Jun 25, 2008 by Chris Walker
 

Question

13 Record last login

Hi Chris,
I see all code of this component, but i can't see where i change to update a field in table Users, after this user make the login action.
I add a field named "lastvisit" in my Users table.

i dont know if a add some code to _login action, or to a function authorize...please, i can help me?
Thanks!
Posted Jul 3, 2008 by Bill
 

Comment

14 RE Record last login

Hi Chris,
I see all code of this component, but i can't see where i change to update a field in table Users, after this user make the login action.
I add a field named "lastvisit" in my Users table.

i dont know if a add some code to _login action, or to a function authorize...please, i can help me?
Thanks!

I reckon the best place to put it is in the "authenticate_from_post()" method, as by POST will mean they have "just" logged in. Change the function so instead of returning "$this->authenticate" you use it in an "if" clause:

Component Class:

<?php function authenticate_from_post($data){
  
$this->from_post true;
  if( 
$this->authenticate($data) ){
    
// Save last login record
    
$this->controller->{$this->user_model_name}->save( ... );
    return 
true;
  }else{
    return 
false;
  }
}
?>
Posted Jul 4, 2008 by Chris Walker
 

Question

15 i cant do it work

I reckon the best place to put it is in the "authenticate_from_post()" method, as by POST will mean they have "just" logged in. Change the function so instead of returning "$this->authenticate" you use it in an "if" clause:
Hi Chris, me again.
I try some things, but i can't do it work correctly.

Component Class:

<?php if($this->authenticate($data))
        {
            
$last_visit date('Y-m-d H:s:i');
            
$this->controller->{$this->user_model_name}->save($data['last_visit'], $last_visit);
            return 
true;
        } else {
            return 
false;
        }
?>
I try this and some other, with
$this->controller->{$this->user_model_name}->save($this->data['User']['last_visit'], $last_visit); and some others, but i cant...
Any idea for me?
My table is User(model User) and the field for user last visit is "last_visit".
You can help me?
Thank you!
Posted Jul 6, 2008 by Bill
 

Comment

16 I solve the problem

Now, i cant update the last_login field in my table.
in my app_controller, i do this:

Controller Class:

<?php 
function _login()
{
if(
is_array($this->data) && array_key_exists('DarkAuth'$this->data))
{
if(
$this->DarkAuth->authenticate_from_post($this->data['DarkAuth'])){
                
$this->data['DarkAuth']['password'] = '';
$usuario $this->DarkAuth->getUserInfo();
$this->User->id $usuario['id'];
$this->User->saveField('last_visit'date(DATE_ATOM));
}
}
}
?>

Thanks Chris
Posted Jul 17, 2008 by Bill
 

Question

17 Undefined variable DarkAuth

if I try to check in the view if somebody is logged in or not, i always get the warning:
Undefined variable: _DarkAuth [APP\views\users\index.ctp, line 6]
if($_DarkAuth['User']['user_id']==$id){
echo "This is your personal page";
}

the user already logged in - the data is in the session - but $_DarkAuth still is empty..

another thing:
if a user is successfully logged out, the session userdata is still present. this usually happens only in IE(7) - not in firefox
i already inserted "no caching" headers - but doesnt work either..

Posted Jul 27, 2008 by Mark
 

Comment

18 funny thing

its $DarkAuth_User not $_DarkAuth_User, as still pointed out on this site..
i figured it out.. :)

i want to point out, that this function could be useful as well (made it up today):

    /**
     * Redirect, if user already is logged in (login/register etc.)
     * @access public
     * @param where_to: e.g. '/CONTROLLER/ACTION' | default: '/users/home'
     */
    function redirectLoggedIn($where_to=null) {
        if (!empty($this->current_user))
        {
            if (!empty($where_to))
            {
                $this->controller->redirect($where_to);
            }
            $this->controller->redirect('/users/home');
        }
    }
Posted Jul 27, 2008 by Mark
 

Comment

19 Feature request

i think it would be nice if i could have var $_DarkAuth = array('except' => array('public_action', 'public_action2')) or even better, in the app_controller having $_DarkAuth = array('except' => array('books/public_action', 'stores/public_action2')) and in the component file something like no_of_failed_logins = 3; such that if a user enters a wrong password thrice, the account is locked and other authentication means are used like favourite pets name, or a series of questions to verify authenticity.
Posted Feb 17, 2009 by Okalany Daniel
 

Question

20 I've a trouble

Hello, I'm cooking my first project. I make your steps for use this great component but there of, don't works fine.

I make all, the relations in the tables, the scripts, for allow the login page I make a route in the apropiate conf file. All runs fine but when I put the string "var $_DarkAuth;" in my users controller for probe, I loss the default style, all changed to a white background and show me a querys from the database. (I can't see nothing only this querys)

When I try to logout I obtain that prompt "Warning (2): Cannot modify header information - headers already sent by (output started at /var/www/gabriela-date-p/app/controllers/app_controller.php:22) [CORE/cake/libs/controller/controller.php, line 615]" and the styles disappear.

I hope that you can help me thanks so much...

Juan
Posted Mar 19, 2009 by Juan David Saab Vanegas
 

Comment

21 RE: I've a trouble

@Juan: You probably forgot to open or close a php tag around line 22 in your app_controller. Also check that there are no more lines after the last closing php tag. Sometimes is just that. Hope it helps.

Hello, I'm cooking my first project. I make your steps for use this great component but there of, don't works fine.

I make all, the relations in the tables, the scripts, for allow the login page I make a route in the apropiate conf file. All runs fine but when I put the string "var $_DarkAuth;" in my users controller for probe, I loss the default style, all changed to a white background and show me a querys from the database. (I can't see nothing only this querys)

When I try to logout I obtain that prompt "Warning (2): Cannot modify header information - headers already sent by (output started at /var/www/gabriela-date-p/app/controllers/app_controller.php:22) [CORE/cake/libs/controller/controller.php, line 615]" and the styles disappear.

I hope that you can help me thanks so much...

Juan
Posted Mar 19, 2009 by Javier Constanzo
 

Comment

22 Yes, It's alive!!!

Thanks for your help, when the file has spaces after de php tags show this kind of errors. But, somebody knows the reason?¿
Posted Mar 19, 2009 by Juan David Saab Vanegas
 

Question

23 Login required in every action...

Hi.

When I run the mySpace action the form login is showed. After login, the mySpace view is showed. Until now, everything is fine.

But when I click the button, the login form is showed again.

What is the problem?

My claimants_controller.php content is:

Controller Class:

<?php 
class ClaimantsController extends AppController {
    var 
$name 'Claimants';

    var 
$_DarkAuth = array(
        
'required' => array(
            
'APPADMIN',
            
'CITIZEN'
        
)
    );

    function 
mySpace() {
        
// Do something...
    
}
    
    function 
myNotifications() {
        
// Do something...
    
}
}
?>

The my_space.ctp content is:

View Template:


<h1>See My Notifications</h1>
<button onClick="JavaScript: window.location = '/projetos/aprovacao/claimants/myNotifications';">My Notifications</strong></button>

Thanks for help.
Posted Mar 20, 2009 by ANTONIO MARCO IDELFONSO DE ALMEIDA
 

Comment

24 PHP Best Practice for Model & Controller files

I suggest we borrow a best practice from CodeIgniter in that we remove any trailing PHP end tags from Model and Controller files. This would eliminate a lot of the problems with cookies, output buffering, etc, because the PHP parser ignores whitespace at the end of a file.

Therefore:

### Start of my_controller.php
<?php
    
class MyController extends AppController
    
{
       
# [...]
    
}
/* end of file my_controller.php */
Posted Apr 6, 2009 by Stephen Holsinger
 

Comment

25 RE: PHP Best Practice for Model & Controller files

@Stephen Holsinger: Maybe your comment belongs in Trac as a feature request. I think it's annoying but we a little experience you can handle it.

I suggest we borrow a best practice from CodeIgniter in that we remove any trailing PHP end tags from Model and Controller files. This would eliminate a lot of the problems with cookies, output buffering, etc, because the PHP parser ignores whitespace at the end of a file.

Therefore:

### Start of my_controller.php
<?php
    
class MyController extends AppController
    
{
       
# [...]
    
}
/* end of file my_controller.php */
Posted Apr 6, 2009 by Javier Constanzo
 

Bug

26 No delete cookie

If you are using cookies and darkauth don't erase it properly, try changing this:


<?php
function setCookieInfo($data,$expiry=0){
      if(
$data === false){
            
//remove cookie!
            
$cookie false
// ...
}
?>

for this:

<?php
function setCookieInfo($data,$expiry=0){
      if(
$data === false){
            
//remove cookie!
            
$cookie ""// <----------
// ...
}
?>

And cookies will be erased forever!!
Good Component!!
Posted Apr 19, 2009 by Cristian Deluxe
 

Comment

27 Admin routing

If you want the component to authenticate all pages with admin routing automatically, add the following line at the bottom of the startup() function:
if (isset($this->controller->params[Configure::read('Routing.admin')])) $this->requiresAuth('Admin');
Posted Apr 29, 2009 by Scott
 

Comment

28 Thanks a million!

This code doesn't quite work with RC1, only a little glitch and easy to fix.

Simply replace the 2 instances of
$this->controller->render to
echo $this->controller->render

I'm using the latest version of cake (1.2.3.8166) and searched for hours why my deny and login pages of restricted controllers are not showing up.. actually nothing was showing up. Then finally found this from the comments..

So yes, I confirm that also. Add the echo and all is fine.

I wonder how there weren't any more info about this? I'm quite new to cakephp, but I thought that this component was very popular.. so anyone with the latest cake builds should have issues with this.

Anyways, please Chris update the component code in the article accordingly!
Posted May 20, 2009 by Anssi Rajakallio
 

Comment

29 Nice Post.

indeed, $error is not set in that piece. Sohbet but that's intentional, Chat as the view will never Muhabbet be rendered. (if that if statement is true, Cinsel Sohbet offcourse)

Hence the $this->redirect in the code, Lez Sohbet and the return statement Bayan Chat (that you left out in your Bayan Sohbet copy-paste, Almanya Chat but it's in the original ;-) Arkadaş Sohbet This will make sure the view will Porno Sohbet not be rendered, Diyarbakır Sohbet and you will be redirected. Lez That's why you don't need to "prepare" Mirc indir the view by setting variables for it.
Posted Mar 19, 2010 by Metin