zen of coding

Demystifying Auth features in CakePHP 1.2

This is actually a quick follow-up to the tutorial I’ve posed recently, which had a few simple, but not very obvious Auth component techniques.

I’d like to cover them in more detail here…

Let’s take a look at the app_controller.php again:

class AppController extends Controller {
    var $components = array('Auth');

    function beforeFilter() {
        $this->Auth->allow('display');
        $this->Auth->loginRedirect = array('controller'=>'users', 'action'=>'index');
    }
}

$this->Auth->allow(‘display’); tells the Auth component to allow a special action called ‘display’. This action is actually part of the Pages Controller, which you can find in the cake’s core. The main purpose of this controller is to serve static pages. You can take a look at the API to see how it works exactly, but one thing to keep in mind is that ‘display’ action is responsible for the… well… display of your static pages. If not allowed, by default, Auth will restrict access to your homepage or ‘About us’ page, for example. This effect is probably not desired in many cases.

$this->Auth->loginRedirect = array(‘controller’=>’users’, ‘action’=>’index’); tells Auth component where to redirect the user after the login. By default Auth would redirect to the main page (root) of your site, or previously visited page… depending on the situation.

Next, in the Users Controller you’ll notice the following:

if($this->action == 'add') {
  $this->Auth->authenticate = $this->User;
}

$this->Auth->authenticate = $this->User; tells the Auth component that it should use the User Model object to override the way hashPasswords() is handled. In other words, if there is a hashPasswords() method in the $this->User object, it will be used instead of the default hashPasswords() method in the Auth controller. As you see, this override only applies during the ‘add’ action.

Why bother?

Well, you might have noticed that by default Auth will hash the password before the validation. This creates a few problems. First of all you can’t apply ‘minLength’ and ‘notEmpty’ rules correctly, since validation method will get the hashed value (which is always long and not empty) rather than the actual value the user typed-in. Secondly, if there are errors in the form, a hashed value will be populated back in the password field.

By making a simple override in the User Model, the little problems and hacks can be avoided:

function hashPasswords($data, $enforce=false) {
   if($enforce && isset($this->data[$this->alias]['password'])) {
              if(!empty($this->data[$this->alias]['password'])) {
                  $this->data[$this->alias]['password'] = Security::hash($this->data[$this->alias]['password'], null, true);
                }
            }

        return $data;
    }

    function beforeSave() {
         $this->hashPasswords(null, true);

         return true;
    }

The benefits are simple, but worthwhile:

  • When building the view I don’t need to be concerned about how the form field name is going to affect my model or controller
  • I can use the field ‘password’ (no need to use some other field name, such as ‘passwd’) to apply my validation rules, as I normally would
  • I do not believe in clearing out the ‘password’ field in case of a form error. There is no need to punish the user, who’ve made a mistake in the ’email’ field by also making him/her re-type the ‘password’
  • You don’t need any additional code in the controller to re-assign/unset virtual ‘password’ fields

To explain the above code, hashPasswords() will not perform the hashing unless the $enforce flag is true, which i will set manually to true in beforeSave().

Hopefully this clarifies some things and gives you some more ideas for the further tweaking of Auth…

  • More and more cool tricks I never knew ($this->Auth->authenticate = $this->User;). Thanks for the time you’re putting in to all of this, teknoid (and for the help the other day with $scripts_for_layout on the google groups).

  • teknoid

    @Brendon Kozlowski

    No problem. That tip by grigri was awesome, learn something new every day.

  • Pingback: Signets remarquables du 07/10/2008 au 10/10/2008 | Cherry on the...()

  • brian

    great help, I got it all working aquickly, but when my user clicks the login link , when they are already logged in, it shows the form again, rather than redirecting them to the login redirect? Should it be smart enough to redirect the, since they are logged in?

  • teknoid

    @brian

    I guess it’s not :) … well it should be easily fixed with one line of code inside your login() action. Just check if user is logged in, and if so, redirect to homepage.

  • Robert

    Something I have a problem with, maybe You have same idea.

    I have to merge userdata from two cake sites. Witch means I need security salt user-based and not application-based. Do You have any idea how to do this without rewriting whole Auth component?

    I thought storing security salt per-user is a standard solution and only found this “application-security-salt-idea” here in Cake. :)

    Anyway thanks for Your blog, I get smarter after every post. ;)

  • teknoid

    @Robert

    Thank you for your comments.

    By overriding hashPasswords(), you can use your own hashing system. That being said, imo, user-based salts might be a little over the top.

  • Robert

    “That being said, imo, user-based salts might be a little over the top.”

    Hmm, I have users with passwords hashed with two different salts, is there any other way? I don’t want to force them to change passwords…

    I will try to mess with hashPasswords(). Thanks again :)

  • teknoid

    @Robert

    If you already have some hashing mechanism in place (and therefore existing user accounts with hashed passwords), then your only option is to override the default used by cake.

  • Lance

    Now if you could publish a post ‘Demystifying ACL features in CakePHP 1.2’, It would be appreciated :)

  • @Lance

    LOL, some day perhaps…
    I’ve been using my own ACL, which I wrote ages ago and so far haven’t had the need to update, but I’m sure I’ll get there sooner or later.

  • nithinalex

    Wonderful article……

  • @nithinalex

    Thank you! :)

  • Pingback: Разъяснения по поводу Auth в CakePHP 1.2 | Bunyan.Ru()

  • Seeing a problem with calling hashPasswords() from beforeSave()… it appears that the already-hashed password now gets re-hashed if updates are made to the User later, ruining the ability to log in. I was implementing an email-based confirmation system (email a one-time-use link to the user for them to follow to confirm their account) for new users and observed this occurring when I saved the user after updating the ‘confirmed’ field.

  • @chuck

    Not quite sure what the issue is, but are you sure you’re authenticating by the User model on the ‘edit’ action as well as ‘add’?

  • brian

    I’d like to use Auth but I’ve got a DB with legacy passwords hashed in an unconventional (for Cake) manner. I’m trying to sort out how I can override Cake’s hashing. Basically my passwords all look like:

    sha1($salt.$password.$salt).$salt;

    … where $salt is a pseudo-random string. For login(), I need to grab the salt off the end of the stored pw in order to salt & hash the submitted pw for comparison. Your post above mentions that authenticate is only useful for add(), which seems to confirm what I’ve learned so far. Is there any way to override Cake’s hashing/comparison? What am I not getting?

  • @brian

    Look at Security::setHash(), you should be able to set any hash method that’s required for your app.

  • jarrod

    Worked wonderfully! Thanks again.

    By the way, small typo with the code:

    if(!emptyempty should be if(!empty in the hashPasswords method

  • @jarrod

    You’re welcome.
    And thanks for catching the error, silly wordpress likes to insert extra code :)

  • Pedro Bessa

    function hashPasswords($data, $enforce=false) {
    if($enforce && isset($this->data[$this->alias][‘password’])) {
    if(!empty($this->data[$this->alias][‘password’])) {
    $this->data[$this->alias][‘password’] = Security::hash
    ($this->data[$this->alias][‘password’], null, true);
    }
    }
    return $data;
    }
    function beforeSave() {
    $this->hashPasswords(null, true);
    return true;
    }

    I tried:
    – the code without changes
    – the code with alias changed to userModel
    – the code with null changed to $data
    – the code with null changed to $this->data

    The codes I tried are not encrypting the password after validating it.
    What else should I try?

  • @Pedro Bessa

    Are you sure the right functions are being triggered?
    Is your model found? (i.e. you’ve named the file correctly as user.php)?

  • Pedro Bessa

    Create Account is hashing the password after validating, but Login Account is not hashing the password at all. What now???

  • @Pedro Bessa

    Now, read the code (and post) more carefully and learn to be a little more polite when someone is trying to help you out ;)

  • Pedro Bessa

    @teknoid

    your questions were very helpful! They made me see the problem in a completely new way. thanks a lot!

    Now, I am wondering if you are still going to help me… I am having this big dilemma here…

    “If I choose to hash the password on create, I can’t hash the password on login. If I choose to hash the password on login, I can’t hash the password on create.”

    This is complicated! This is the kind of problem that I can’t solve alone! Are you still going to help me?

  • @Pedro Bessa

    I can certainly try, if you ask nicely :)
    Unfortunately your previous comment did not come across that way… but let’s move on…

    For one, the password should be hashed on login (if you followed my example), because you only override the default hashing method on the “add” action (see the second code snippet).

    Therefore, during login Auth should take over password hashing and compare the hashed value to the one you have already stored in the DB. Please take a look at the SQL debug output to see if that’s really the case.

  • The issue I’m having is that the beforeSave() method gets triggered, and thus re-hashes the already hashed password, when I save the user record again to do an update on it — in my case, to set the ‘confirmed’ field to 1 once the user has clicked the confirm link in their confirmation email. It would happen again on /any/ subsequent update of a User record. Is there a reliable way at save-time to determine whether it’s a new record creation or an update?

  • Checking whether the id before hashing the password is set is one possible way around this, but might not be the best:

    function beforeSave() {
    if (!isset($this->data[$this->alias][‘id’])) {
    // hash password on user creation, but not on updates
    $this->hashPasswords(null, true);
    }
    return true;
    }

  • @chuck

    This is a reasonable solution. You might also want to check

     || !isset($this->id)

    , as a Model id might be set without the field being present in the data array. As you notice, for this example, I only override the default hashing of the passwords on the ‘add’ action just to keep things simple.

  • That only affects whether Auth hashes the /input/ password before handing it along to the controller. By putting the call to hashPasswords() in the beforeSave() method and having it modify $this->data, you’ve made sure that it rehashes the /already stored in the database/ password on /any/ save of a User model. beforeSave() does not care what controller action was run.

  • @chuck

    I completely understand your point. The thing is when providing examples of code, you don’t always over complicate the task at hand, by adding code that may or may not be necessarily applicable (that’s why I said just to keep it simple).

    By the way how would that work if the user does need to change their password? There are couple of common scenarios that could affect the way beforeSave() should be structured. I’m hoping to provide enough food for thought without making the code obnoxious, so that it can be expanded and improved upon, such as you’ve done.

  • That’s a good question. It’s a worthwhile goal to not make the code obnoxious… what I think is obnoxious is that Auth’s behavior forces us to write obnoxious workaround code for common, normal things like creating users and changing passwords.

  • btw, in answer to your question about how that would work for changing passwords, it doesn’t. The password ends up going to the database unhashed.

  • @chuck

    Fair enough ;)

    Yeah, I figured it might cause a problem in that situation.
    An extra level of check such as a custom-made _isPasswordModified() method, might be required to work around that (just a hint).

  • Peter Childs

    As someone learning both php and Cake I refer to your site frequently – and read the comments. My question seems to be an extension of the discussion in the last few comments but I’m still very much a newbie so hope this is appropriate.

    I used the custom hashing method listed here to make sign-up easier – and then tried to generalize by using it as part of my password update process – so I could have the same hashing and validation benefits.

    I add my ‘passwordupdateform’ action to the beforeFilter by doing this:
    function beforeFilter()
    { parent::beforeFilter();
    if($this->action == (‘sign_up’ || ‘passwordupdateform’ ))
    Now when I ‘sign_up’ or ‘passwordupdate’ the process works flawlessly (passwords not hashed until save) etc.

    The problem comes when I try to ‘login’. If the ‘passwordupdateform’ is listed in the beforeFilter login fails – remove it and it succeeds.

    I wonder if you have any ideas as to what could cause this? I gather from above that it is related to the beforeSave – but a I can’t understand how that would affect ‘login’ – as the hashed password is read and compared but not saved.

  • @Peter Childs

    Interesting, somebody else mentioned a similar problem with the login. I wonder if something had changed in the core since the writing that would be causing this problem.
    The default hashing method should only be overridden when a specific action is called, so on login Auth should hash and compare the password (as it usually does) to the hashed one in the DB. Could you verify by looking at the SQL debug, that that indeed takes place?

  • Peter Childs

    Is this the SQLdebug you’re looking for?

    SELECT `User`.`id`, `User`.`password`, `User`.`email_address`, `User`.`userName`, `User`.`UserType`, `User`.`Created`, `User`.`Modified`, `User`.`Expires`, `User`.`active`, `User`.`FirstName`, `User`.`LastName`, `User`.`SMS`, `User`.`DefaultScale`, `User`.`Grocer` FROM `users` AS `User` WHERE `User`.`email_address` = ‘peter@bmail.org’ AND `User`.`password` = ‘peter33’ AND `User`.`active` >= 1 LIMIT 1

    Based on User ‘peter@bmail.org’ and password ‘peter33’ (actually just updated with ‘passwordupdateform’ in beforeFilter) then an immediate test to log in)

    If I then remove ‘passwordupdateform’ from beforeFilter and test again – and there is no SQL debug because logon succeeds and I’m redirected as expected.

    If it’s the wrong debug let me know and I’ll try to get what you need.

  • @Peter Childs

    No, that’s exactly it.
    Not that it really clarifies or explains things :)

    For whatever reason the login() method is not hashing the password as evident from the query. And you say it only happens when you have ‘passwordupdateform’ listed in the condition. Does the same work when you only have $this->action == (‘sign_up’)… or you need to remove the entire condition (and password hashing override)?

  • Peter Childs

    I see (after I posted) that we’re not comparing a hashed password in the first case – which would of course cause the login to fail.

    Could this have anything to do with differences in the way save behaves when it creates a new record vs updates an existing one? or am I grasping at strays.

  • Peter Childs

    If it’s only sign_up the login occurs normally – but the passwordupdate fails because the password and password_compare don’t match.

  • @Peter Childs

    Right, but it’s baffling that it works with one action in the condition, but not with an OR.
    Out of curiosity, can you try your condition like so:

    if(in_array($this->action, array(‘sign_up’, ‘passwordupdate’))) {
    $this->Auth->authenticate = $this->User;
    }

    I don’t really see what could be the problem otherwise.

  • Peter Childs

    Thanks – that works perfectly.

    I’ve noticed a few other places where 1.2 stable seems to prefer the array(‘function’) format – but because I’m learning it didn’t occur to me to try that.

    Thanks again.

  • @Peter Childs

    Wow. I am actually quite surprised that this has worked.
    Btw, that’s really not a cake feature, but good ol’ PHP… I still wonder why.

  • @teknoid: Although I haven’t thoroughly tested it, I don’t think the test case code:

    if($this->action == (’sign_up’ || ‘passwordupdateform’ ))

    …should be written like that. Almost all programming languages I’ve taken have always said it needs to be explicitly stated such as:

    if($this->action == ’sign_up’ || $this->action == ‘passwordupdateform’)

    The in_array trick is a nice alternative if you have many possible strings you wish to test against. I actually almost used it today with your code here, but decided against it (for now) as I only needed it for 2 actions. If it ever grows to more than 2, I’d also use in_array for (my own?) readability.

    Anyway, my question…
    Do you see any benefit to doing things this way (overriding Auth’s hashing in beforeFilter) as opposed to validating the password itself in the beforeFilter method instead?

  • While I was waiting for my post to be submitted, and the page to reload, I realized the answer to my own question. By using beforeFilter, if we wanted to show all validation errors on the rendered view, we would have to validate all fields (and I believe in beforeFilter, we need to manually invalidate the fields, one by one). With this method, we can simply allow the call to $this->Model->save() do all of the dirty work.

    If I’m mistaken about validate/invalidate, let me know! :D

  • @Brendon Kozlowski

    I must’ve been seriously drunk, not to see that obvious error in the if statement :)

    As far as your question, definitely the validation should be handled in the model. If anything, it’s proper MVC and we keep our model fat and controller skinny.

  • @Brendon Kozlowski

    And your own argument is quite good as well… ;)

  • Hi, teknoid your post is great,
    now, i’m build some app with cake, and so thankfully for find your blog,
    other things are great besides your post, you’re really help them to out of the problems,
    very appreciated ;)

  • @taufiq

    Thanks, good to hear if helped you out ;)

  • Just wanted to thank you for this great post.

    This is exactly what I was looking for. No more hashing of empty passwords. Cheers!

  • @Scott Reeves

    Nice, good to hear :)

  • Pingback: Aidan Lister » Blog Archive » Creating a community in five minutes with CakePHP()

  • Pingback: CakePHP and Auth Component Password Hashing | Miscellaneous()

  • gedetok

    I have a problem with

    “Login failed. Invalid username or password.” in Auth Component

    array(2) {

    [“_method”]=>

    string(4) “POST”

    [“data”]=>

    array(1) {

    [“User”]=>

    array(2) {

    [“username”]=>

    string(7) “demo123”

    [“password”]=>

    string(7) “demo123”

    }

    }

    }

    array(1) {

    [“User”]=>

    array(2) {

    [“username”]=>

    string(7) “demo123”

    [“password”]=>

    NULL

    }

    }

    Query:
    SELECT `User`.`id`, `User`.`username`, `User`.`password`, `User`.`hp`, `User`.`email`, `User`.`firstName`, `User`.`lastName`, `User`.`alamat`, `User`.`pin` FROM `users` AS `User` WHERE `User`.`username` = ‘demo123’ AND `User`.`password` = ‘b7d2e4fbb2819b948b88ca603f21096e17a3ec3c’ LIMIT 1

    can you tell me what should I do.

  • @gedetok

    Take a look at your query, does that result make sense?
    Also, please post this question at the google group or IRC. This is not the best place to get help.

  • @chuck

    I’ve modified teknoid’s method a bit to check if the password that we get form the client in fact already exists in our database (thus the user is updated but the password has not changed), if so, skip hashing, if not, hash.

    function hashPasswords($data, $enforce=false) {
    if($enforce && isset($this->data[$this->alias][‘password’])) {
    $user = $this->find(‘first’, array(‘conditions’ => array(‘OR’ => array(‘User.id’ => $this->data[$this->alias][‘id’], ‘User.id’ => $this->id))));
    if($user[$this->alias][‘password’] != $this->data[$this->alias][‘password’]) {
    $this->data[$this->alias][‘password’] = Security::hash($this->data[$this->alias][‘password’], null, true);
    }
    }

    return $data;
    }

  • I’m adding a user through ajax. The beforeSave is not working. I’ve tried to call hassPasswords in my ajax function but no luck. The has is generated for empty password field and I cannot validate.

  • @irfaan

    I’m not sure how you are doing this exactly, but if you are calling (let’s say) ajax_add_user() in your controller… and all-in-all it does a good ol’ $this->User->save($ajaxData); there is no reason for beforeSave() not to work.

    The server side handles saving… so, all callbacks should kick-in as normal. I see no reason why ajax call would be any different than a regular form post.

  • Pingback: CakePHP Auth Compontent Management | 365 Web Applications for Designers()

  • Tylër

    Thank you for this:

    Security::hash(

    =)

    vlw!

  • Teknoid,
    Thanks for this article. A related question is, how does an admin set a ‘global password’ that allows him to log into any user account, if Auth is being used to log each individual user in? I am trying to avoid having to create an admin interface to do this, and a global password would fit our needs nicely – I just havent found a way to do this without hacking the Auth component in the core – any ideas would be appreciated.
    Kunal

%d bloggers like this: