Sometimes also called magic links, these login links provide a way for a user to log in without the need of password entering.
Basic workflow
Instead of a "password reset" button to send you a mail to confirm your password change, the login page has a "send me a login link" button.
You enter the same email, but the result now is a bit different.
The mail contains a direct link that will log you in automatically.
This is – if the email sending is secure – a de-facto two-factor auth.
And similarly safe than the reset password functionality, since that is the same email sending process.
Alternatively, you could also send a code (e.g. via phone messaging) to have to enter.
These one time logins should expire after usage, and also be quite limited in time (e.g. hours).
Passwordless login
If you take this one step further, you can even make the login completely passwordless.
- User registers and gets the login link sent via email
- User clicks the link and is directly logged in
And for the next visit
- User enters email to send a login link again
- Users logs in again
Implementation in CakePHP
We can build our link sending directly on top of Authentication plugin and the documented auth process.
The login link functionality can be found in Tools plugin if you just want to quickly re-use it.
For this we add our identifier and authenticator after the already existing ones in Application.php:
service->loadIdentifier('Tools.LoginLink', [
'resolver' => [
'className' => 'Authentication.Orm',
],
]);
// Session, Form, Cookie first
$service->loadAuthenticator('Tools.LoginLink', [
'urlChecker' => 'Authentication.CakeRouter',
'url' => [
'prefix' => false,
'plugin' => false,
'controller' => 'Account',
'action' => 'login',
],
]);
Now you should be able to log your users in on top of classic form login.
Mail sending
Send your user(s) an email with a login link.
Four our new authenticator the expectation is your login action with ?token={token}
appended.
You can generate one using:
$tokensTable = $this->fetchTable('Tools.Tokens');
$token = $tokensTable->newKey('login_link', null, $user->id);
If you want a custom token to be used:
$token = $tokensTable->newKey('login_link', '123', $user->id);
The mail sending is usually done via queue process. If you want to use the Queue plugin:
$queuedJobsTable = TableRegistry::getTableLocator()->get('Queue.QueuedJobs');
$data = [
'to' => $user->email,
'toName' => $user->full_name,
'subject' => __('Login link'),
'template' => 'login_link',
'vars' => compact('token'),
];
$queuedJobsTable->createJob('Email', $data);
The template can be as simple as
A login-link has been requested for this email/user.
By clicking this link you will get logged in:
<?php echo $this->Url->build(['controller' => 'Account', 'action' => 'login', '?' => ['token' => $token]], ['fullBase' => true]);?>
Mark email as confirmed
When you not just send login link emails to already verified users, but if you
replace the registration login process with such quick-logins, you might need to
use a callback to set the email active when the authenticator gets called.
This way your custom finder (e.g. 'active'
) will actually find the user then.
$service->loadIdentifier('Tools.LoginLink', [
...
'preCallback' => function (int $id): void {
// Sets email_confirmed
TableRegistry::getTableLocator()->get('Users')->confirmEmail($id);
},
]);
So once the user has been activated through a valid email (and this link click) in the pre-callback, the identifier is now able to find and return the user.
Security note
When implementing the "send login link" functionality, similar to "password reset", you do not want to expose if that email is a valid user email on your system.
So make sure the response is the same in both cases:
You validate the input and if format is valid, you continue. Return always a successful "If this email is in our system, you will receive a link" kind of text.
You ideally send the emails via Queue, as normal sending could be "measurable slower" due to the API call, and as such might also give an attacker information about the validity of an active email.
Summary
The authentication plugin is powerful and flexible. Adding a custom authenticator like this "LoginLink" on top is quite quick and easy.
Personally, I do like my passwords. Once the browser saved them for this page and form, I can just click+click and I am back in.
Additionally, I use a password manager to keep track of them.
In cases where there is no information or on a mobile device it can sometimes be handy to quickly send yourself this email with a simple click to log back in.
And if this avoids a double-opt-in (e.g. with Google Authenticator app), then that’s also super fast for users.
Let me know what you think, if this is something you would want to add to your apps.