introduction to dAuth v0.3
This article introduces dAuth V0.3: the authentication (not authorisation) system with a focus on security, using techniques such as challenge-response, customizable multiple-stage password hashing, brute force (hammering) detection etc.
May i introduce... dAuth v0.3
dAuth is basically plug and play. get all the code that you'll find at the end of the article, execute the sql and you're all set. You can optionally tinker with the variables in the component to suit more specific needs.You could actually stop reading here and just start playing with it. But if you're interested in the more in depth workings, read on.
Before you start
The dAuth system is based on the original "Challengeresponse authentication with fallback" article. This version will be referred to as dAuth v0.1 from now on.http://bakery.cakephp.org/articles/view/128
I received quite some positive feedback about both v0.1 and v0.2 and it made me feel good. (thumbs up for the big chefs, they make this all happen!)
If you haven't read the previous article, i suggest you do it now, as i won't repeat all the necessary information here.
Keep in mind dAuth is only meant for authentication (is someone who he claims to be?) and not authorization (is someone allowed to do this or that?)
Also, don't use dAuth v0.1 or v0.2 code, it contains nothing that v0.3 doesn't have :) (apart from a few bugs ;-)
dAuth in depth
dAuth is a challenge-reponse authentication system.Here is a diagramn of the transitions the password goes through.
Changes since previous versions
v0.3 has evolved quite a bit since v0.1.
Genericification (if that's a word)
Let's start with the most simple change. v0.3 is more generic then v0.1 because...- i tried to organise the code in a way that makes most sense (put the auth logic in a component, let the users controller make use of the component, have the right functions in the right places (eg moved some logic to models memberfunctions)
- i replaced some view code with calls to cake's helper functions. This decreases performance a very little bit, and makes the code a tiny little bit more complicated, but the code is more generic, and that's what this is all about. (cheers Robert and Andy Dawson for the hints)
Detection and handling of brute-force attempts (hammering)
When someone tries to login, a new entry in the hosts table is created, with his ip-adress in it, or an already existing one is retrieved. The behaviour of this host is checked (using the $hammerRatio variable) and if needed, the host is denied access and put in a blocked status. It's possible to immediatly die() the execution if the host is blocked, or hammering is detected. (you choose this all with $diehammer and $dieblocked)The blocking is effective for the timespan you define in $blockTime.
When discussing this with Crazylegs (othman ouahbi), he mentioned he has a similar system, but uses cookies to do additional checking: when bots/scripts would change their ip-adress, they would still have the cookie, and the auth system could detect this. Personally i'm not sure about the benefits of such an approach (as i doubt if scripts/bots just accept cookies, especially if they are smart enough to change ip's. i could also imagine several servers that try to enter many websites and switch their task to the other server when they get blocked, in this case it wouldn't work too) but it certainly is worth investigating.
Since i don't like to depend on crontabs, old (unneeded) loginAttempt records are deleted about every once in 500 http login requests with the dAuth _cleanUpAttempts() function.
Preventing users transmitting plaintext passes (disabling fallback)
set the $allowClearText variable in the component and you're set! how easy can it be :)If $allowClearText = true; then the default form will be working, the user can fill in his details (username, password) and submit the form. Upon submit, if he has javascript, a hash will be created of the password, and the password itself will be replaced by a dummy string.
If he has no javascript, he will transmit the pass in cleartext, and the system will accept the login if the information is correct.
If $allowClearText = false, however, there will be a form with empty action and method fields, and with style set to invisible, accompanied by an error that the user should get javascript support if he wants to login. If he doesn't have javascript enabled, he will thus only see the error, and not the form.
However, if he has javascript enabled, the contents of the error will be emptied, the form method and action will be correctly filled in, and the form will be set to visible. (all happening immediatly at page-load) This results in exactly the same form as he would otherwise get with $allowClearText set to true. This way, the user can login normally, with the hashing and other fancy features enabled. :-)
Session hijacking detection & handling
This is an aspect where cake already handles quite a lot, by providing a decent session class with issues like fixations and impersonations already in mind.I found a great article about security issues arising from using sessions: http://phpsec.org/projects/guide/4.html it mentions 4 aspects regarding session hijacking:
- Session id prediction: This means just guessing the identifier, and is a pretty unsuccessfull approach to session hijacking. Id's are randomly generated, and consist of many digits, so it's very unlikely to guess a valid session identifier. Even if found one, it's probably of not much use because you will need to do more to hijack the session. (see below) and the chances that your guessed (valid) id is actually the one belonging to your target, is also pretty small.
- Session id capturing: session id's are transferred via GET variables or cookies, data which is sent in cleartext over the network, and could be sniffed by bad guys, or retrieved via bugs or potentially other methods. I guess it could even be done serverside if you're on a shared host: a crewmember could retrieve your session files/entries in the database. I should look into this issue further, but keep in mind that getting the valid session id (with this method, or the id prediction method or the fixation method below) is not the only step towards a successful hijacking. read on...
- Session fixation: The key here is that a malicious user passes you a url with a (randomly chosen) session id already in it, if you keep that session alive by browsing the site and logging in, the malicious user knows a valid session id by which he could hijack your session. The article recommends generating a new random id when the user logs in. CakePHP is our buddy here because it systematically generates a new session id each http request, totally crushing fixation attempts! This also has the side-effect of making a successfully retrieved session id only valid for a very short timespan: the time between successive http requests or in worst case, the maximum session valid time in case the "last" session id is retrieved. This valid time is defined in the config file. see http://manual.cakephp.org/chapter/configuration (it is also used to delete the serverside tmp session files after expiring)
- Target impersonation: So, if the malicious user retrieved your session id one way or another, he should not be able to hijack your session. How can be checked if a request with a valid session id is coming from a good guy are a bad one? By checking not only the session id, but also additional parameters like the user agent. Again, cake spoils the fun and takes away any need to program this in the application code ( ;-) ) It has hijack-protection built-in by checking the user agent. However, personally i think this could be improved, because it's isn't very hard for a malicious user to try some known user agents, especially since some are very popular (most targets probably use MSIE6 for example, and all the frequently used user agents are not only known (they are everywhere on the internet), they are also a select few.) A system that would block users that had more then one user agent in a short timespan would be of use here i think. (or let them auth again). Even more, i would just use the ip-adress as additional criteria to check your user against. (this is not recommended by the article, but personally i've never seen one user having more then one ip-adress in successive http requests. But even if this would be the case, letting the user login again is just a small sacrifice. On the other hand, the chance that the good guy and the bad guy share the same ip is very small, and faking an other ip adress is an extra step that makes it even harder to take-over a session (maybe something to think about for later cake versions, activating ip-check when security is set to high?)
Small changes
- I've changed the name of the encrypt() function to hash(), because strictly speaking, that function isn't really encryption because by definition, in that case decryption should be possible. It's rather hashing, which is irreversible (which doesn't mean that one can't find passwords for a given hash, see previous article. But this can take very long, especially if you make this function more complicated then using plain md5's or sha's)
- You'll see that hash() is no longer just a sha1 hash. To protect against password-retrieval efforts i added a salt. And not just a static salt, since the salt is the first letter of the password itself, the salt is customized for each password which makes using password-cracking tools harder :-)
- I improved the error reporting mechanism a bit. Different errors while processing the user input in the business layer will result in warning messages that are directly linked to the specific problem.
- and more... (see for yourself :)
These were all changes from v0.1 to v0.2
There are also quite a few specific additions specific to v0.3, you'll see those at the specific pages mentioned below.
Installation
Option 1
Here are the pages with the code:- views, usersController and javascripts http://bakery.cakephp.org/articles/view/152
- component http://bakery.cakephp.org/articles/view/153
- helper http://bakery.cakephp.org/articles/view/154
- models http://bakery.cakephp.org/articles/view/155
Option 2
you could also just grab the code from these urls:- views/users/login.thtml: http://bakery.cakephp.org/articles/download_code/152/block/1
- views/users/register.thtml: http://bakery.cakephp.org/articles/download_code/152/block/2
- views/elements/userinfo.thtml: http://bakery.cakephp.org/articles/download_code/152/block/3
- views/users/change_password.thtml: http://bakery.cakephp.org/articles/download_code/152/block/4
- controllers/users_controller.php: http://bakery.cakephp.org/articles/download_code/152/block/5
- webroot/js/d_auth.js: http://bakery.cakephp.org/articles/download_code/152/block/6
- webroot/js/sha1.js: http://bakery.cakephp.org/articles/download_code/152/block/7
- controllers/components/d_auth.php: http://bakery.cakephp.org/articles/download_code/153/block/1
- views/helpers/d_auth.php: http://bakery.cakephp.org/articles/download_code/154/block/1
- models/user.php: http://bakery.cakephp.org/articles/download_code/155/block/1
- models/host.php: http://bakery.cakephp.org/articles/download_code/155/block/2
- models/login_attempt.php: http://bakery.cakephp.org/articles/download_code/155/block/3
- SQL code: http://bakery.cakephp.org/articles/download_code/155/block/4
Don't forget you need to follow cake's conventions!http://manual.cakephp.org/appendix/conventions
Configuration
Take a look at the variables in the component, change to your likings and you're all set!Final words
You'll see that i've added a little bonus, a basic (no captcha or other fancy stuff yet) /users/register and /users/changePassword form/action, and a userinfo element which strictly speaking can't be part of an auth-system, but since it they integrate so nicely with the auth system and share some logic i just have to do it ;-). Even if you don't want any visitor to register on your site, you can limit this action to yourself to easily create new users. (the right component's stage1Hash() function is called for you so the perfect hash is put in the database) Ironicly enough, when filling in the register or change password form, the passwords can not be hashed using stage2Hash, since this hashing manner changes all the time and the server would not be able to know the original stage1 hash that is the "source" of the stage 2 hash. And the database needs to store the stage1 hash so... If anyone would sniff the network, and if they are smart enough, they could enter the sniffed hash in the form, modify the javascript code a bit to start from the stage1 hash (which would be created anyway) instead of the original password, in order to generate valid responses for the challenge/response system. Https would certainly be in its place here, or otherwise a customized encryption system so that the server could decrypt the password.Also, keep in mind that i'm just human. Humans make errors, especially humans like me! Take a look at the code, try some stuff out, and give us some feedback, thanks! (especially the (timing stuff in) the host behaviour checking could use some attention)
To-do
- Look into session id capturing, how it can be done, if cake does something about it, or if i should do it
- Consider using cookies as add-on for hammering detection (with possibly denying users if they disable cookies, or using a fallback for these users, with the same security issue remaining)
- Find out how i can request the SSL layer if it's available
- Find out how can encrypt and decrypt efficiently for using the register and password-change forms.
Comments
Bug
1 MySQL version limitation
version 4.0.27. Unfortunately, "collate utf8_bin" is only supported under 4.1 and later.
Comment
2 fixed
good point, i removed those pieces. they shouldn't be necessary anyway.
Question
3 Error after install
Fatal error: Class 'L' not found in /media/docs/krowe/html/cTrack/cake/libs/model/model_php5.php on line 481
Contents of model_php5.php
function __constructLinkedModel($assoc, $className)
{
$colKey = Inflector::underscore($className);
if(ClassRegistry::isKeySet($colKey))
{
$this->{$className} = ClassRegistry::getObject($colKey);
}
else
{
$this->{$className} = new $className(); // <-- this is the line that throws the error
}
$this->alias[$assoc] = $this->{$className}->table;
$this->tableToModel[$this->{$className}->table] = $className;
$this->modelToTable[$className] = $this->{$className}->table;
}
I am very new at using this but I have a few pages working on my site and believe I have my routes properly setup, but just in case here they are:
$Route->connect('/users/newpass/', array('controller'=>'users', 'action'=>'changePassword'), 'change_password');
$Route->connect('/users/register/', array('controller'=>'users', 'action'=>'register'), 'register');
/* $Route->connect('/users/logout/', array('controller'=>'users', 'action'=>'logout'), 'login'); */
$Route->connect('/users/*', array('controller'=>'users', 'action'=>'login'), 'login');
Any help would be appreciated. Thanks.
Question
4 User check
loggedIn()): ?> Stuff for authed users
It would be really sweet, and it'd make it easy to actually use this stuff. Also, it would be great to have functions to retrieve the actually logged username and stuff. Or just tell me how to get it done, and I'd do it myself.
Thanks a lot.
Comment
5 reply to Chris and Kevin
in the dAuthComponent add this at the end of the startup function:
[code php] $User = $this->Session->read('User');
$this->controller->set('User',$User);
This sets the userdata from the session available in your presentation layer. (Like this you don't even need helper functions, you could for example put something like this in any of your views, layouts or elements:
View Template:
<?php
if($User) echo ('Logged in as '.$User['username']);
else echo 'not logged in';
?>
Comment
6 reply to Chris and Kevin part 2
Bug
7 undescore missing
It reads:
$hosts = $this->controller->Host->findAllByIpAdress($ip);
Should be:
$hosts = $this->controller->Host->findAllByIp_Adress($ip);
since your field an has an underscore in it.
Great component!!
Comment
8 bug fixed
more info:
http://groups.google.com/group/cake-php/browse_thread/thread/859d121726ece828/b062a3db8c435c32 https://trac.cakephp.org/ticket/1567
i'll add it as a comment in the component and include the fix in future releases.
Comment
9 Not a bug
SQL Error in model Host: 1054: Unknown column 'Host.ip__adress' in 'where clause'
Works for me as is... thank you Dieter.
Comment
10 camelcase vs underscores
proposal by othman: works on php4 (i think, can't check), not on php5
fix that i posted: works on php4 and php5 ( http://bakery.cakephp.org/articles/view/153 )
Comment
11 Great news
I fixed all found bugs and added some features. I also updated the article a bit.
Have fun with v0.3 !
Dieter
Bug
12 Some minor bugs
Some extra bugs to report:
Under d_auth.jsp doStage2 function:
current: var hash = stage2Hash(stage1Hash(password) + salt);
Should be: var hash = stage2Hash(stage1Hash(password),salt);
Took me few hours to find this problem. Do change it asap.
Under users_controller.php:
For those who does not have it, add the RequestHandler Component with:
var $components = array('dAuth','RequestHandler');
And the current syntax of the RequestHandler method calls are in lower case.
current: $this->requestHandler->getClientIP()
change to: $this->RequestHandler->getClientIP()
That is all. Hope these changes are useful.
Comment
13 oops
I'll fix it right now!
Comment
14 Javascript not enabled warning in IE
Pardon it is me again. In IE, the warning:
"Login impossible. For security reasons, you should enable javascript."
appears even when IE has javascript running. Firefox works nicely. I am not too sure is it me and my whole office, we does that the javascript no enabled message.
A work around was to change the message from a
to a .
Under dAuth helper formheader function:
Current:
$output = "";
Change to:
$output = "";
It works and I guess there should not be much side effect to the display. Hope this is useful again.
Bug
15 Minor bug
Simple put the letter 't' to uppercase
here:
$this->set('allowcleartext', $this->DAuth->allowCleartext);
to
$this->set('allowcleartext', $this->DAuth->allowClearText);
^
Bug
16 bugfixes
Redhex, i tried ie 5,5.5 and 6, and with all the versions i get the error message, but that might have something to do with the fact that i have problems enabling JS in my IE browsers (on linux) ;-) I think it's very strange that the showing or not of an error message is defined by the use of a html tag... But i'll keep an eye open ... ;-)
So ftr,
bugfix #1 for v0.3: users_controller.php line 80/81: change allowCleartext to allowClearText
Dieter
Comment
17 Re
As for the "headers already sent" error.. check line 441 of cake/libs/controller/controller.php, something must be causing some output
I have no idea what version of cake you use so i can't check for you
Question
18 Headers already sent error
Thanks for the speedy response.
Sorry, I forgot to say which version I was using. I am using the latest released version of Cake - v1.1.12.4205 under xampp and Windows XP.
The line 441 in controller.php is the line in the redirect function which does "header('Location: ' . $url);"
The data already sent to the page comes from cake\libs\view\view.php:308 which is in function render which does a "print $out;".
This, I believe, is called by the controller->flash when the registration is successful on line 195 of the component d_auth.php
The other major problem I have is that the hashed password stored in my database is the same regardless of what password I enter for registration (and any subsequent login fails).
Bug
19 SHA1 .js problem
Ok. I seem to have resolved the hash problem. It appears that the implementation of the sha1 algorithm in sha1.js at http://bakery.cakephp.org/articles/view/152 is now not the same as that by Chris Veness at http://www.movable-type.co.uk/scripts/SHA-1.html. If I replace the algorithm in ths d_auth sha1.js with this implementation, the hash does change and the login does work.
Comment
20 Re
the first bug is the one that doesn't allow you to download the "real" code https://trac.cakephp.org/ticket/1810 the 2nd bug is the one that considers some characters as tokens to start a change in layout, hence not only *** the layout of the code, but also leaving out a few chars from the code. https://trac.cakephp.org/ticket/1663 (i've put a warning on that bakery page that you shouldn't copy paste the code like that but i guess i should have been more clear)
Comment
21 Shouldnt Authentication
like this one: http://www.ejeliot.com/pages/2
I think this would help, wouldn't?
Comment
22 Captchas
I will maybe implement them someday, but captcha's are -to me- a feature with low priority, as there are aspects that are much more important (and often neglected) when focusing on a decent, secure authentication system. Not to mention that actually, they just don't have much use for authencation...
Bug
23 form data is never empty
If you set debug to dump the objects it is clear that the hidden elements hold data
[form] => Array
(
[data] => Array
(
[User] => Array
(
[username] =>
[password] =>
[hashed_pw] => 8930d9ac7e3b2cdfe67b22e71b9022a4036e9a67
[special_sauce] => 261053480
)
)
)
Looks like ($this->data) will always contain hashed_pw and special_sauce.... or am I missing something?
Comment
24 data checking
The if (!empty($this->data)) test, is to check whether the action is called for the first time (the user hasn't filled in any form and wants to login) or if he is sent there from a form.
If the user is coming from the form, special_sauce will always be filled, hashed_pw will be filled if javascript is enabled clientside.
If the user just goes to users/login for the first time (without filling the login form), $this->data will be empty.
Deeper checking of the contents of $this->data happen deeper inside the component (eg checking username, password,...)
Dieter
Question
25 Email as username
Great work. Really like what your doing :)
I often uses an email address instead of an username.
It would be nice if you implemented a feature in the component so you could switch between email and username as login credentials.
Now I have to change this every time you release a new version.
Comment
26 user choosable identifiers
Dieter
Bug
27 No login details slight modification
###############
AT THE END OF
function attemptLogin($postUser = null,$ip = null)
{
$success = false;
$cleartext = true;
...
if($cleanHost)
{
// Changed from 0.2
if(is_array($postUser) && !empty($postUser) && isset($postUser['User']) &&
isset($postUser['User']['username']) && isset($postUser['User']['password']))
Change this to:
if(!empty($postUser) && isset($postUser['User']) &&
isset($postUser['User']['username']) && isset($postUser['User']['password'])) // note the is_array was removed
At the end of this if block, you must include the } else { statement.
.. which reads:
} else {
$this->error = $this->messages['empty_login'];
} // close post validation
} // close if CleanHost
return $success;
} // close function
Also, add this to the $messages variable:
var $messages = array( ...,
'empty_login' => 'Empty Credentials' );
######################
Changes I have also made in my version of your work (0.2):
1) Group rights, is the user allowed to use this admin/user function?
2) Email as the login information
3) Redirect to login page if you need to be logged in to view a certain page. After successful login, the login page redirects back to the same page.
I will post these 'add-ons' when I update to 0.3 and fix a couple bugs.
Comment
28 no bugs but some good ideas
I think everything you say has been fixed.
First of all: in v0.3 the userscontroller checks is $this->data is empty or not. if it's not empty it sends it to the component, after that the component does additional checks to see if $this->data is an array, if it contains elements , if $this->data['User'] exists etc etc. If $this->data is empty, it just shows a new login form. (or shows it again). Imho this is normal behaviour
Then your extra ideas:
1) dAuth is an authentication system, not an authorisation system. I recommend using cake's ACL-system to do access control. (along with one of the components that has been published on the bakery for example). The upcoming dAuth v0.4 will be perfectly compatible (and very easy to integrate) with adsix' acl component.
2) good idea, i'll implement it (see few posts higher)
3) Also a good idea, i'll implement that too.
Bug
29 Problems with other languages than english
May be you could modify the helper to separate the label and the id of the field
* In echo $dAuth->formPassword('Password','User/password'); Label and ID are the same, if I call the field "Mot de passe" (password in french), it cannot be encrypted.
Thank you again
Yann
Bug
30 id of password in helper must be kept default
I consider this a bug.
The api of the dAuth helper isn't very well constructed btw. I mean it works, and it is as secure as i wanted it to be (unless we discover more bugs ofcourse ;-)), but it isn't very flexible when it comes to choosing your own styles, error messages, or labels. You can easily fix/hack it yourself, but i will make it better in the next version :)
So ftr,
bugfix #1 for v0.3: users_controller.php line 80/81: change allowCleartext to allowClearText
bugfix #2 for v0.3: don't change the id of the password in the views, or make a little change to the helper to make it work.
Bug
31 May be a little confusion
In the users_controller
you have var $helpers = array('Javascript','DAuth');
var $components = array('DAuth','RequestHandler');
I changed it into
var $helpers = array('Javascript','dAuth');
var $components = array('DAuth','RequestHandler');
to avoid any confusion between component and helper that generates an error.
Cheers
Yann
Comment
32 Redirect to entry page after login
In the function checkSession which is in the appController
function checkSession()
{
// If the session info hasn't been set...
if (!$this->Session->check('User'))
{
$this->Session->write('login_referrer',$this->params['url']['url']);
// Force the user to login
$this->redirect('/users/login');
exit();
}
}
And in the login() function in the user controller we place the follow code:
if ($success){
// some codes you might have goes here.
if ($this->Session->check('login_referrer'))
{
$loginReferrer = $this->Session->read('login_referrer');
$this->Session->delete('login_referrer'); $this->redirect($loginReferrer);
return true;
}
// the line before is the original redirect code
$this->dAuth->redirect('login_success');
return true;
}
That should be all. I am looking forward to dAuth v0.4. It had been a really good component for me so far.
Comment
33 Reply to Dieter
The above code works with the DAuth user views, but if I try to use the PHP code in the views of another model/ controller, it fails. Can you please guide me how to achieve that. I used the default blog tutorial and tried to add DAuth to it.
There is no associates of the blog posts with users as of now. Will adding that help or can I do it some other way so that any other views of different models can also access the same.
Cheers,
JKG
Comment
34 blog tutorial
The elements however, are usable in any view or layout.
Comment
35 Redirect to entry page after login (with variable passing)
function checkSession($vars = NULL) {
// If the session info hasn't been set...
if (!$this->Session->check('Customer'))
{
$redirect = array('link' => $_SERVER['REQUEST_URI'], 'vars' => $vars);
// Force the user to login
$this->Session->write('redirect',$redirect);
$this->redirect('/customers/login');
}
}
now add anywhere you want to check the session or pass the variables->>>
//Authenticate
$this->checkSession($_POST);
now to read the information when it comes back after login -->>>>>>
$redirect = $this->Session->read('redirect');
$this->set('redirect', $redirect);
if($redirect['vars'] != '' && $redirect['vars']['exampe_number'] != 0){
} else {
// user never sent data through the form (hack)
}
Question
36 Header Issue
Warning: Cannot modify header information - headers already sent by (output started at /home/xxxxx/public_html/dev/app/models/user.php:39) in /home/xxxxx/public_html/dev/cake/libs/controller/controller.php on line 441I can tell that where the error is actually being generated is when the redirect function is being called in d_auth.php. Looking at my source code I don't see and rendered HTML other than the error, so I am perplexed... any ideas??
Comment
37 Headers Followup
Comment
38 Header Issue
In general, this problem rises up when you have a space before the first When you cut/paste some code, you often put an extra space.
Yann
Question
39 May I have some salt
Thank you for your availability as a 'chef pâtissier'. I understand well your clever distinction between login et transport but I scanned all your code and don't understand why I am not able to login on the login page, my credentials are always mismatched with the password on the database, I have a problem of salt - an important item to make a good cake... May be I did not pick up the good code or something... I give up ;=)
Yann
Comment
40 headers already sent
so Andy, check your user.php
Comment
41 Salt
I also read you've been changing some code. Maybe try to remember if you made any changes to the code, and what they were (version control rules!).
If you can't figure it out i suggest you install it again from scratch, but fixing the 2 bugs as explained above. If then it still doesn't work post here again and try to be more detailed (which variables aren't set for example?) because without further information i can't magically guess what might cause your problem (especially since you seem to be the only one having this problem)
Also, are you sure there is the right hash in the database? did you change the stage1Hash or stage2Hash functions? if you change one of them in the component, you have to change it in the helper too, and vice versa.
Also, your hash in the database will become invalid if you change a hashing function.
Comment
42 salt or pepper
Sorry for my bad communication. I did not changed anything from your code, I just implemented it inside my app and all works well except that I can't log in.
My critical point is in the component :
$real_hash = $this->stage2Hash($dbUser['User']['password'],$salt);
$submitted_hash = $postUser['User']['hashed_pw'];
if($real_hash == $submitted_hash)
{
$success = true;
}
I get $success false.
I suppose that when I log in for the first time, I would not have to go through this part of code, this part is aimed for those who are logged in yet and have a session.
Is that correct ?
Thank you
Yann
Comment
43 THANKS
Great component... you are saving me WEEKS of agonizing development!
Comment
44 open source salt aka NaCl
Jean Carfantan : post a topic on the google group, i'll help you there, and be more detailed (maybe read a tutorial about debugging)
for instance, what are the values of $real_hash, $dbUser['User']['password'] and $submitted_hash? if we can't find the problem there, we'll dig deeper in the php/javascript code until we find it.
Question
45 User variable
I'm using your most recent code. When I try and look at the $User variable in any of my views, it returns an undefined variable, even if I have logged in. I have checked and the two lines:
$User = $this->Session->read('User');
$this->controller->set('User',$User);
Are at the end of my startup function. Any ideas?
Thanks,
Andy
Comment
46 Session
Comment
47 The whole process
Thank you for your proposition. I thought about giving you the different values but I have here a problem of process or a problem of comprehension.
You have two functions :
* the first one uses the first letter of the password in clear as salt, then put it into the database
* the second one uses the crc32(time()) to generate a new salt to transport the password along the pages
When you log in you need the first algorithm to compare with the hash inside the database and you need the second one to put a new and more secure hash inside the session.
My problem here is that it is the second hash that is compared with the hash inside the database.
Tell me, Dieter, if my argument is correct.
Jean
Comment
48 userdata in view and an explanation about the hashing
$sessionUser = $this->Session->read('User');
if(!$sessionUser || !is_array($sessionUser) || empty($sessionUser))
{
$sessionUser = false;
}
$this->set('User',$sessionUser);
Jean we don't "transport a password along pages". We transfer it from client to server.
And "the second function" aka stage2Hash doesn't call crc32(). The component does this on its own, then it's put in the session and later on it's retrieved from the session to call stage2Hash()
And no, there are several cases:
1) no javascript enabled @ client+ fallback not allowed -> user doesn't get a valid form, gets error message and can't login.
2) no javascript enabled @ client, but fallback is allowed: the password is sent in cleartext over the network, the server receives this, and does a stage1Hash() on it to compare it with the stage1 hash stored inside the database
3) javascript enabled @ client: javascript computes the stage2 hash in 2 stages (first stage1, then stage2, based on the salt that it gets from the server), then sends this stage2 hash to the server. the server grabs the salt from the session and does a stage2Hash() on the stage1 that it retrieves from the databases, and then compares the stage2 hashes.
There is no such thing as "making the password more secure to put it in the session", it works just like described above, the session doesn't have much to do with it (it does contain the userdata that is retrieved from the database, which includes the stage1 hash.)
I reviewed my login code btw and i don't see where it would compare a stage2 hash against the stage1 from the database. Please tell me at which point this happens.
Comment
49 My data
Here is my data
username = ravel
password = pavane
I get this
Real hash : 903a6e1948193c9da492c4a06aa571816b0f274a
Submitted hash : b174737ff0ba3f2ce29f20ec792ac25209fb76b9
Jean
Comment
50 google group
Question
51 A couple of questions.
I also added the following block to my pages controller.
$error = '';
if (!empty($this->data))
{
$success = $this->DAuth->attemptLogin($this->params['data'],$this->RequestHandler->getClientIP());
if($success)
{
$this->DAuth->redirect('login_success');
return true;
}
else
{
$error = $this->DAuth->getErrorMessage();
if(!$error)
{
$error = $this->noReason;
}
}
}
$this->DAuth->newSalt();
$this->set('error', $error);
$this->set('allowcleartext', $this->DAuth->allowClearText);
I also check for whether or not I am currently within the users controller in my default layout. If I am *not* and the user is *not* logged in I include the login form. If they are in the users controller or are logged in they see their user info. I did this because having the javascript included in the element was interfering with the javascript in the register form. ( I assume I was regenerating a hash that shouldn't have been regenerated )
This works fine in that it registers users and logs in and out just fine. The one *bug* or issue I found and would like help with is that once registration is complete I get a message ( with debug > 0 ) that I am missing a ../layouts//.thtml file - Yes, 2 slashes and a blank filename. It also appears the redirect stopped working for some reason ( or it is not succeeding? )
The user accounts *are* created and work fine.
Any ideas or instructions on how to include a login form on all pages if the user is logged out and their info when they are logged in without breaking the hashes or registration form?
Thanks
Abba Bryant
Comment
52 userinfo element and using form throughout entire site
Not necessarily the whole action (which also checks if post data coming from a login-form is included) but at least the last 3 lines:
$this->DAuth->newSalt();
$this->set('error', $error);
$this->set('allowcleartext',$this->DAuth->allowClearText);
This will do the necessary preparations to make a login form work.
(obviously the DAuth component must be available in the AppController for this)
If you want to show userdata I recommend using (and modifying until it does what you want) the userinfo element you can find here:
http://bakery.cakephp.org/articles/view/152
As for your layout problem, this is about code that you wrote, so if you want help with i recommend you create a thread on the google group and post at least your code.
Bug
53 login form is not xhtml 1.0
Comment
54 ToDo 3 request SSL if it is available
echo $_SERVER['SERVER_PORT'];
HTTP = Port 80
HTTPS (SSL) = Port 443
Comment
55 Thanks
Comment
56 reply to wjz and Chris
@Chris: thanks for the idea, but more then that needs to be done (to "check" if it's avaible), I was thinking about trying a redirect to a https://* url, but I haven't yet been busy with this idea, it will come though... :-)
Comment
57 repeat password
Since the password is changed into the hash when it gets to the server then i guess it has to be on client side with javascript, or should I hash both fields and validate serverside?
I would be great if this could be on the next version ;)
Comment
58 repeat password
First of all you should hash both fields. It wouldn't be very secure otherwise ;-)
I'm not much experienced with custom validations, but I think writing a function (or overriding an internal one) for this in the model is a good idea: just to check if the hashes (or cleartext passes, when submitted in cleartext with fallback enabled) match and invalidate the model when they don't. then you'd just show the form again with an error message.
Comment
59 Error in host.php model file
I think, in file host.php is mistake at this (cca 81) line:
$attempts = $this->LoginAttempt->findCount(array('host_id' => ' = '.$data['Host']['id'],'LoginAttempt.created' => '>= '.gmdate("Y-m-d H:i:s", $limit)));
generated this SQL (in mssql) wrong question:
SELECT COUNT(*) AS count FROM [login_attempts] AS [LoginAttempt] LEFT JOIN [hosts] AS [Host] ON ([LoginAttempt].[host_id] = [Host].[id]) WHERE [host_id] = ' = 31' AND [LoginAttempt].[created] >= '2007-05-03 21:22:06'
There is problem here : "WHERE [host_id] = ' = 31' "
I think that the line sould be:
$attempts = $this->LoginAttempt->findCount(array('host_id' => $data['Host']['id'],'LoginAttempt.created' => '>= '.gmdate("Y-m-d H:i:s", $limit)));
Thanks for this auth component Dieter.
Mareg
Comment
60 bugfixes
What version of cake are you using btw? Maybe this behavior has been changed in the unstable 1.2 tree
This has been reported for postgresql too btw. check comment # 2 on this page: http://bakery.cakephp.org/articles/view/dauth-v0-3-models
the funny thing is that his fix is just removing the space before the equal sign, while your fix is removing the equal sign all together. I'll look further into this to find the approach that should work on all databases, although i assume that both fixes work equally well. (i think cake will check if there is an equals sign (without space before it) and if it isn't there add one automatically)
So ftr:
bugfix #1 for v0.3: users_controller.php line 80/81: change allowCleartext to allowClearText
bugfix #2 for v0.3: don't change the id of the password in the views, or make a little change to the helper to make it
bugfix #3 for v0.3: remove default '0000-00-00 00:00:00' from sql of models.
bugfix #4 for v0.3: change 'host_id' => ' = '.$data['Host']['id'] to 'host_id' => '= '.$data['Host']['id'] (remove the space before the equals sign)
Bug
61 Blank password is not detected in register function
On the register view, if I enter all but Password field (leave empty), the registration is successful when it should not be. Looking at the database, there is a hashed password being stored.
1) Is the JS code is generating a hashed password even when the password field is blank? Aka, using blank as a valid value? Because in the database, I always see the same hashed value.
2) In your dAuth component, attemptRegister function, you are not catching an empty password field. For example:
$this->controller->User->create();
$user['User]['email'] = $postUser[user] $user[user][password] = $hash
In this last statement, you should first check to see whether $postUser[email][password] was set and has contents. It's only if then, the PostUser[user][hased_pw] is a hashed equivalent of something other than a blank password.
Please let me know if my analysis is on the right track.
Cheers.
Comment
62 Strange IMG tag bug
DAuth sets a random number in $this->Session->write('salt', $salt); and when I read that key with $this->Session->read('salt'); after I posted the form it was a different number then before the post. But this was only when I called it from the controller /newsarticles/index/, when I used the form in the controller /users/login/ the number salt was right. So I figured some code in the /newsarticles/index/ controller messes with the session vars. After checking all the code the thing messing up the session var was......
An
I don't know why it messes with the session, but it does. Maybe some .htaccess thing?
But to use this component as an element do this:
Model Class:
<?php
class AppController extends Controller
{
var $helpers = array('Html', 'Javascript', 'DAuth');
var $components = array('DAuth','Session');
function beforeRender()
{
// Begin DAuth component code
$this->DAuth->newSalt();
$errorLogin = $this->DAuth->getErrorMessage();
if (!$errorLogin) {
$errorLogin = 'Unknown reason.';
}
$this->set('errorLogin', $errorLogin);
$this->set('allowcleartext',$this->DAuth->allowClearText);
// End DAuth component code
}
}
?>
Default.thtml:
View Template:
echo $this->renderElement('loginbox', array('errorLogin' => $errorLogin, 'allowcleartext' => $allowcleartext, 'special_sauce' => $special_sauce));
That's it :) Great component btw!
Comment
63 General announcement and reply to Vaz and Jordy
I don't have the time right now to check this, but I sure believe it behaves like that because I didn't explicitly program anything to encounter it. So you could hack the javascript to send an empty hash or something for an empty password so the serverside code can detect this and act accordingly (form validation)
I would like to announce that it seems like in the forseeable future, I will no longer have the time to maintain dAuth. At the end of this comment you'll see the current list with bugs, most of them are not critical but it's good to take a look at it anyway and patch dAuth yourself.
Jordy: I think it's a known - but very rare bug - somebody once blogged about it (the title was "cakephp: always make sure you set your image paths correctly" or something like that)
bugfix #1 for v0.3: users_controller.php line 80/81: change allowCleartext to allowClearText
bugfix #2 for v0.3: don't change the id of the password in the views, or make a little change to the helper to make it
bugfix #3 for v0.3: remove default '0000-00-00 00:00:00' from sql of models.
bugfix #4 for v0.3: change 'host_id' => ' = '.$data['Host']['id'] to 'host_id' => '= '.$data['Host']['id'] (remove the space before the equals sign)
bugfix #5 for v0.3: don't allow registration with empty password. (patch the d_auth javascript)
Comment
64 design issue
For the users convenience, we create a dummy, random string of the same length as the password, so that the user doesn't notice his password has changed as he tries to submit the form.
This is bad design because this is a hint for sniffers: if they sniff your traffic they can know the length of the password. They still don't know the password itself so it's not a security leak but still this is not good practice... yet another reason why we should use https.
Read more about why i recommend https and about me not longer having time for cake/dauth on my blog:
http://dieter.plaetinck.be/bye_cakephp_bye_dauth_hello_drupal
Comment
65 dAuth with Ajax and side order of fries
I already had a simple registration system in place, but when I re-read your features-list on dAuth, I switched! Thank you. Not only did you provide encrypted login that doesn't require SSL, you've added IDS too. And with well-written code. Huzzah!
I'm integrating your core code with Ajax-powered forms, because I want a login box embedded in the default layout that changes to a 'home' and 'logout' links box when you're logged in.
When it's ready, I'll be happy to share the code with you.
Thanks!
Paul Wolborsky
Comment
66 Header errors seem to have really mundane cause
Make sure those tags are airtight, and you'll get rid of the errors.
Paul