DarkAuth v1.3 - an alternative Auth

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

Update: 14th May 2008

After some discussion regarding this component (thanks Sam), I thought I would point out at this stage that the component is really geared towards "Role-based" authentication, something I hadn't even noticed at while building it.

That being said, it makes it even more unlikely that it would be used in a non-HABTM type situation - so be aware if you are thinking of using this is a "User belongsTo Group" that you may find it hard to get the fluid access control you want.

On the other hand if you are using it purely to restrict access with NO group control, then it couldn't be easier!


Firstly thanks to everyone who encouraged me after the first release of DarkAuth. Looking back at it, it was flawed yet I got support from the community and have worked to better it.

Again, I'd better whet your appetite with just how easy it is to use.

Let's say you have a SecretsController which you want to restrict access to:

Controller Class:

Download code <?php 
class SecretsController extends AppController {
  var 
$name 'Secrets';
  var 
$_DarkAuth;

...

}
?>

See what I did there? Now you'd have to be logged in to access this Controller. So, now you want to refine this to allow only logged on users that are a member of the group Admin or the group SecretKeepers. Easy!

Controller Class:

Download code <?php 
class SecretsController extends AppController {
  var 
$name 'Secrets';
  var 
$_DarkAuth = array('required'=>array('Admin','SecretKeepers'));

...

}
?>

Not bad eh? So moving on, by default your users if not allowed access will see your specified "deny" page, but you want them to be redirected back to /public. Again, straightforward.

Controller Class:

Download code <?php 
class SecretsController extends AppController {
  var 
$name 'Secrets';
  var 
$_DarkAuth = array(
         
'required'=>array('Admin','SecretKeepers'),
         
'onDeny'=>'/public'
      
);

...

}
?>

Now, any attempt to access your SecretsController will be presented with a login page if not logged in and if logged in but not a member of the group, will be redirected to /public.

This can be done per action as well, to further enhance the usefullness of the component. E.g. you have a DocumentsController which might have some top secret documents in it. You only want members of the group TopBrass to be able to see them.

Controller Class:

Download code <?php 
  
function display($id=0){
    
$this->Document->id $id;
    
$doc $this->Document->read();

    if(
$doc['Document']['top_secret'] == true){
       
$this->DarkAuth->requiresAuth('TopBrass');
    }
    ...

  }
?>

Note that the function DarkAuth::requiresAuth() stops processing after you call it and will bring up an "access denied" view if authentication fails. So no need to exit().

Or more common if you wanted to show content based on whether authentication was present? The DarkAuth::isAllowed() function returns whether access is allowed, but doesn't stop processing:

Controller Class:

Download code <?php 
  
if($this->DarkAuth->isAllowed(array('ChocolateLover'))){
    
$data $this->CookieJar->findAll(array('Chocolate'=>true));
  }else{
    
$data $this->CookieJar->findAll(array('Chocolate'=>false));
  }
?>

The final selling point (in my opinion)! $_DarkAuth available in the View, automatically populated with the user info from the user model and the access control list. e.g.

View Template:

Download code
<?php
  pr
($_DarkAuth);
?>

Yields if logged in:

Download code
array(
      'User' => array(
                      'id' => 1
                      'username' => superstar
                      'password' => abcdef1234567890abcdef1234567890
                      'other_info' => Some data
                )
  'Access' => array(
                    'group_you_have_access_to' => 1
                    'another_group_you_have_access_to' => 1
                    'group_you_have_NO_access_to' => 0
              )
)

Which means you can do this:

View Template:

Download code
<?php
if(!empty($_DarkAuth['User'])){ 
  echo 
"Some content for logged in people!";
}
if(
$_DarkAuth['Access']['some_group']){
  echo 
"You have access to 'some_group'";
}else{
  echo 
"You don't have access to 'some_group'";
}
?>

Convinced? I hope so. Now on the Code and Setup!

Page 2: The component itself

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