The CakeDC organization behind CakePHP has created the Users plugin, that has all the features a usual web app has - user login, registration and so on. However, the plugin by default assumes that the user logins with a username and password. For simplicity, some websites might prefer that the user logins with their email as their username.
The documentation for the Users plugin shows how the user can be made to login with their email, but the user still has to create a username during registration. Fortunately, by extending the plugin, it’s quite easy to remove all mentions of username and make everything work with just an email address as the login - the username field can even be removed from the database.
This guide is written for CakePHP 3.5.x and the plugin version 6.0.0.
Login with email and password
To begin, follow the documentation referenced above and change the authentication to use the email field and modify the login template to ask for email instead of username. (To overwrite the templates, copy them from vendor/cakedc/users/src/Template/Users
to src/Template/Plugin/CakeDC/Users/Users/
).
In addition, you have to go through all other templates and edit them so that there are no references to username
, either remove or replace with email
as appropriate (fe. remove in add.ctp
and register.ctp
, use email
instead in profile.ctp
).
Also make sure that in config/users.php
or wherever you configure the plugin that the Users.Email.required
is set to true
.
Database migrations
If you used the provided migrations to create the Users table, note that the username
was set to NOT NULL
so you need to either create a migration that either completely removes the column or allows null values for it. By default, the database also allows null values for email
field, so you might want to do the exact opposite for it. Or you can just manually edit the table to do both. A sample migration is given below.
As mentioned you can just removeColumn('username')
but this would be an irreversible migration so you would need to define up()
and down()
migrations for that.
Extend the Users model
Your Users model will very likely have relationships to other tables, so in practice you will have to extend the plugin’s Users model for your own purposes. The plugin’s own documentation about extending uses src/Model/Table/MyUsersTable.php
and src/Model/Entity/MyUser.php
but you can use src/Model/Table/UsersTable.php
and src/Model/Entity/User.php
by using the as
keyword when importing the plugin’s classes.
There’s a lot going on in our extended UserTable
. We need to override some of the plugin’s methods that rely on username
field existing. First, we setDisplayField('email')
and override validationDefault()
to remove validations relating to username
field. In addition, we have to extend the PasswordBehavior
so we have to remove the plugin’s own that searches users by username and add our own that does not.
By overriding the getter and setter for username
in the model, all operations for that field will be effectively no-ops that will return (but not modify!) the email address instead. Because the plugin separates first and last name, you could also define _getFullName()
as explained in CakePHP’s documentation on virtual properties.
By default, the plugin’s PasswordBehavior
allows the user to recover their account if they remember their username or email. However, in our version, we want to only search for email. Fortunately, it is very easy to extend the behavior so that this happens.
The original method will use findByUsernameOrEmail
which is not safe in our version (and will fail if you removed the username
field from the database table). Make sure you updated the template to reflect this change. (This will affect localizations.)
And finally, remember to change the plugin’s configuration in config/users.php
to use your subclassed model instead.
Done
Now you should have the plugin overridden safely so that any call to the username
property in the model will return email
instead, and all other places were the plugin wants to validate the fields contents or use it in a feature are subclassed so that it doesn’t. In addition, these changes should be somewhat future-proof unless the internals of the plugin change a lot in a future upgrade.