Heavy duty password validation in CakePHP
When validating passwords for new users or changing passwords for existent users there are three main concerns.
- You want passwords to match
- You want passwords to be ‘strong’ (meaning complex)
- You want to store the passwords under some encryption
*Strong in this case means a minimum of 8 characters with no whitespace. It must contain upper case and lower case letters, and at least 1 digit or special character.
If your using 1.2 RC1 or higher, jump to here.
The caveat there is obvious, if your using one way encryption like md5, you must validate before you encrypt. Using the built in var $validate will gooey this all up, forcing you to hash before you save and then trying instead to validate the hashed password.
So instead I wrote a very simple function for use in the user model to validate against all three of these concerns.
If you just want to get some regular expressions to use in PHP or other languages, read this article.
Otherwise you may find this implementation useful.
This article is outdated for newer releases of CakePHP. Instead please see my article on validation in CakePHP 1.2
The User Controller and Registration Action
First we’ll have a look in the users controller’s registration action. You’ll notice we don’t mess with the password or validation, and just call our save method as normal.
in app/controllers/user_controller.php
if ($this->User->save($this->data)) { /* * Data was saved successfully */ $this->Session->setFlash('The User has been registered, please login'); $this->redirect('/'); } |
Wow, thats almost like a fresh baked action.. good sign.
The Model and Validate function
Now we leverage a built in function call validates() to check for our pattern, check for matching passwords, and finally hash the prevailing password before saving.
In app/models/user.php
/* * This method gets called automagiclly for us, and does 3 things * validates against a regular expression, ensuring it is 'strong' * confirms the user entered the same password twice * if both above passs, it hashes the surviving password to be saved */ function validates($options = array()) { $pass=$this->validatePassword(); return $pass; } function validatePassword(){ //die($this->data['User']['password'].' :: '.$this->data['User']['confirmpassword']); if(isset($this->data['User']['confirmpassword'])){ if(!preg_match('/(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/',$this->data['User']['password'])){ //doesnt mneet our 1 upper, one lower, 1 digit or special character require,ent $this->invalidate('password'); $this->data['User']['password']=null; }elseif($this->data['User']['password']!=$this->data['User']['confirmpassword']){ $this->invalidate('checkpassword'); //they didnt condifrm password }else{ //hash passwordbefore saving $this->data['User']['password']=md5($this->data['User']['password']); } } $errors = $this->invalidFields(); return count($errors) == 0; } |
So the idea is that we call the models save method, which in turn calls our validates method. The alidates method will print cause one of two error messages, or allow the successful save.
So where do those two error messages go? But in our views of course. Since I’m using the register action for this example, I will stick with my register view
The Registration View and Dual Validation Error messages
So we have two text boxes, password and confirm password.
We also use 2 error messages, one for bad pattern matching or ‘weak ‘ password the other for missing password confirmation, or bad password confirmation.
in app/views/users/register.ctp
<div class="required"> <?php echo $form->label('User/password', 'Password');?> <?php echo $form->password('User/password', array('size' => '30'));?> <?php echo $form->error('User/password', 'The password must contain both upper case and lower case characters with at least 1 digit or special character');?> </div> <div class="required"> <?php echo $form->label('User/confirmpassword', 'Confirm Password');?> <?php echo $form->password('User/confirmpassword', array('size' => '30'));?> <?php echo $form->error('User/checkpassword', 'Please Be Sure Passwords Match.');?> </div> |
1.2 Users
For CakePHP 1.2 we can follow a slightly different, and in my opinion an preferable way.
1.2 Does a nice job of allowing for custom Validation. You can set criteria within your model, and even set all error messages to prevent typos and redundant data. Best of all we can call a function to run in place of a rule for more complex actions (like checking for match then hashing)
Read my more recent article ‘Complex Validation with CakePHP 1.2‘
When overriding a framework method, make sure to use the same method signature as the original method, otherwise you may get an unwanted effect 😉 In your case this means to use the following signature:
validates($options = array())
@Daniel
Much appreciated! I’ll make the change in my code and the post.
Is this for CakePHP 1.1? The $validate methods for 1.2 are a lot cleaner 🙂
@Richard
This works on 1.1 or 1.2
I’m not sure I understand your meaning, the $validate variable in the models? How would you match 2 passwords, check for a high-security pattern, and then finally hash? If you have a sweeter way, please share 🙂
Apologies:
A few poster comments were lost this last week due to a DB restore.
Please do not take offense, and re-post at will.
I just had to do this myself but followed some other tutes so went a different route:
I placed treatment of the password, hashing and so forth in beforeSave().
Used a function placed in app_model to see if the passwords match:
http://bakery.cakephp.org/articles/view/using-equalto-validation-to-compare-two-form-fields
Then placed the regex for testing quality of the password in the validate rules:
‘password’ => array(‘isRequired’ => array(‘rule’ => array(‘custom’, ‘/(?=^.{7,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/’),
‘required’ => true,
‘allowEmpty’ => false,
‘on’ => ‘create’,
‘message’ => ‘Password should be at least 7 characters, must have at least 1 upper case, lower case and numeric or special character.’),
‘identicalFieldValues’ => array(‘rule’ => array(‘identicalFieldValues’, ‘password_confirm’ ),
‘message’ => ‘Passwords do not match.’,
‘on’ => ‘create’))
Also used multiple validation sets which were handy:
http://snook.ca/archives/cakephp/multiple_validation_sets_cakephp/
Using 1.2, no auth component.