Here, we build a complete community based website in five minutes using Cake best practices, with the following features: account registration, login and logout, account management page, and password retrieval.
We will take the following steps:
Bake a new Cake website
Build an account registration page
Create a login page, and optionally add a lastlogin field
Add change password functionality to our account page
I’m going to assume that you have a working cake shell installation, so change to your DocumentRoot and execute:
$ cake bake community
This will create a community folder with all the necessary files for your new website. If you visit http://example.com/community, you should see the “all green” indicating you have a working cake installation.
Next, we’ll create the users table by executing the following SQL:
That’s it for the setting up … Let’s move on.
Step 2. Build an account registration page
We’ll get straight into it by creating the users controller, model and the register view.
Because of CakePHP’s automatic hashing, we have to do all our validation work on the password_confirm field, as the password field contains only the hash of the users password.
Next we create our view at views/users/register.ctp. Because we’ve done all our validation on the password_confirm field, the error messages (password too short, etc.) will appear under password_confirm. This isn’t ideal from the user interface perspective; luckily we can just swap the input labels and noone is the wiser.
Finally we create our users controller at controllers/users_controller.php. We define a beforeFilter method to make our registration page publicly accessible, our register action, and a blank login action.
The next issue with Auth arises when the user has entered a valid password, but has failed to validate the entire form - for example, if they picked a username that is in use and must be re-entered. As the password has already been replaced by the hashed password, the form will be redisplayed with the hashed password in the password input. If the user attempts to resubmit the form without retyping their password, form validation will fail on the password_confirm field. Even worse, if you’re not using a password_confirm field, the user will simply be unable to log in to their newly created account.
Thus, we need some hackery to make sure encrypted passwords are not sent back to the user. We can do this in AppController, by writing a beforeRender method.
And that’s it, we have a complete working registration page. We can view this completed page by visiting /users/register and create our first account.
Step 3. Create a login page
Next up is our login page. We need to modify the default layout which lives at views/layouts/default.ctp. We replace the content div which allows Auth to display messages to the user.
Next we create a very simple login page:
And that’s it - it doesn’t get much simpler. We can view our new login page at /users/login.
If we wanted to track the time the user logged in last, we can do some extra work:
Adding to the AppController, we set Auth->autoRedirect:
Then we modify our login controller to do the necessary work:
Not a lot of additional work, and it will come in handy if you need to prune inactive accounts. Note we can’t this part of the step yet, as it’s expecting an /users/account page to exist (which we create next).
Step 4. Add change password functionality
Cake’s form validation functionality is somewhat limited, in that it only allows you to define validation rules at the model level. Often, you will want to validate data at a per-form level instead. To achieve this, we can dynamically modify the validation rules before form validation takes place. We dive into the user model again:
Note: If we simply added the password_old rule to our validation behaviour, other forms that didn’t include password_old would not validate (because we have set required = true). If we removed required = true, a POST request can easily be forged bypassing this security check.
Our change password logic now becomes very simple. We validate, then update the password and redirect the user.
Note: In this case Cake decides not to automatically hash the users password field, so we must perform the hashing manually. This happens because Cake will only hash the password if both the username and password key are set.
We create a template for the account page as follows:
The user is now able to change their password. We can test this by logging in at /users/login which will take us to our new account page.
Step 5. Create a password retrieval page
In our final step, we allow the user to retrieve a forgotten password. We do this by emailing the user a token. The user clicks back to our website with the token, and gets emailed a replacement password.
We create a models/token.php to handle the token generation, information storage and retrieval.
We next update the users controller to make the recover and verify actions public, then we define our recover and verify actions:
Because we are generating new passwords for users, we need a function to do this. Alternative approaches, like PEAR’s Text/Password can be substituted if you like.
We define two email templates for recovery and verification respectively:
And finally we define our two templates for the recovery and verification views:
And that’s it … we’ve developed a complete community orientated website for CakePHP in less time it takes than to make a cup of tea (if you’re very slow at making tea).
On a side note: you may have noticed that CakePHP’s automatic hashing makes things a lot harder than they should be. In my opinion, automatic hashing directly goes against Cake’s mantra of “If it’s easy to do in Cake, then it doesn’t belong in core”, and I hope the core development team rethink in the idea in Cake 1.3.x.x. Although forcing developers to automatically hash their users password is a nice idea, the current implementation a major stumbling block for new bakers. We shouldn’t need a section in the manual about common problems the user will encounter trying to do simple tasks. Instead, we should fix the cause of the common problems. It is, however, a testament to Cake’s flexibility that we are able to work around most of these issues quite elegantly. It’s also worth noting that there are several ways to override this behavior.
I hope you’ve enjoyed this tutorial, please feel free to leave comments, suggestions or improvements.