DarkAuth v1.3 - an alternative Auth
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
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!
Comments
Bug
1 Variable variable needed at line 308ish
// 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!
Bug
2 Lillte Bug on the tutorial
var $_DarkAuth = array('requires'=>array('Admin','SecretKeepers'));
When actually we have to use 'required' insted of 'requires'.
Just this litlle thing ....
You component rlz :)
Question
3 Not quite working...
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.
Comment
4 Some Replies
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.
Question
5 DarkAuth and output buffering
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"?
Comment
6 Problem with your source file...
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..
Comment
7 RE DarkAuth and output buffering
@Scott, Thanks, that's almost certinaly the cause.
Bug
8 DarkAuth and RC1
Simply replace the 2 instances of
$this->controller->rendertoecho $this->controller->renderQuestion
9 User view your self profile.
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
Comment
10 RE User view your self profile
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!
Comment
11 Cannot make it work
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.-
Comment
12 RE Cannot make it work
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
Question
13 Record last login
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!
Comment
14 RE Record last login
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;
}
}?>
Question
15 i cant do it work
Hi Chris, me again.
I try some things, but i can't do it work correctly.
Component Class:
<?php if($this->authenticate($data))I try this and some other, with{
$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;
}
?>
$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!
Comment
16 I solve the problem
in my app_controller, i do this:
Controller Class:
<?phpfunction _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
Question
17 Undefined variable DarkAuth
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..
Comment
18 funny thing
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');
}
}
Comment
19 Feature request
Question
20 I've a trouble
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
Comment
21 RE: I've a trouble
Comment
22 Yes, It's alive!!!
Question
23 Login required in every action...
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.
Comment
24 PHP Best Practice for Model & Controller files
Therefore:
### Start of my_controller.php
<?php
class MyController extends AppController
{
# [...]
}
/* end of file my_controller.php */
Comment
25 RE: PHP Best Practice for Model & Controller files
Bug
26 No delete cookie
<?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!!
Comment
27 Admin routing
if (isset($this->controller->params[Configure::read('Routing.admin')])) $this->requiresAuth('Admin');Comment
28 Thanks a million!
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!