DarkAuth v1.3 - an alternative Auth
3 : Setup Steps (5 little ones)
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
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):
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.
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.
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:
And a page for /app/views/deny.ctp:
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.
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:
NB any controller would do, but you're pretty sure to have a UsersController.
That's it. Hopefully you haven't had too many issues, and your App is now secure and happy.
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
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!
Comment
29 Nice Post.
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.