A package to handle one-time passwords (OTP) in Laravel apps
-in-laravel-apps.jpg)
I’m proud to share that we have released a new package called spatie/laravel-one-time-passwords.
Using this package, you can securely create and send one-time passwords via mail, SMS or any channel that you like. Of course, it also provides an easy way to verify a one-time password.
The package also ships with a Livewire component to allow users to login using a one-time password.
In true Spatie fashion, almost anything in the package can be easily customized.
What are one-time passwords?
One-time passwords (OTPs) offer a convenient and secure way to authenticate users without requiring them to remember complex passwords.
They're single-use codes that expire after a short time period, typically sent to a user's email or phone number.
OTPs are ideal for scenarios like:
- passwordless login: Allow users to access their accounts by simply receiving a code via email or SMS
- two-factor authentication: Add an extra security layer beyond just username and password
While OTPs are simple by design (often just 6 digits or less), our package implements several security measures to prevent abuse:
- OTPs expire quickly (2 minutes by default)
- They must be used from the same origin (IP address and user agent) where they were created
- Only one OTP can be active per user at any time
- Each code becomes invalid immediately after use
- Time boxing to prevent brute force attacks
All of these security features have sane defaults, but they can be configured to your liking.
Creating and consuming one-time passwords
One of the first things you should do when installing the package, is to update your User
model to use the HasOneTimePasswords
trait.
namespace App\Models;
use Spatie\OneTimePasswords\Models\Concerns\HasOneTimePasswords;
class User
{
use HasOneTimePasswords;
// ...
}
This will add methods to store and retrieve OTPs in the one_time_passwords
table.
The easiest way of sending a one time password to a user is to call sendOneTimePassword
.
$user->sendOneTimePassword();
This will create a one-time password and send it to the user's email using the OneTimePasswordNotification
-notification. Of course, this notification can be customized to your liking.
Here’s how that mail notification will look like by default.
Alternatively, you could also create a one-time password without actually sending it.
$oneTimePasswordModel = $user->createOneTimePassword();
This method will return an instance of a newly created OneTimePassword
model, that you can use in your custom way of delivering the password to the user.
When implementing your login flow using one-time passwords, you can use the attemptLoginUsingOneTimePassword
method which will verify the given one-time password and log in the user.
Here's an example:
use Spatie\OneTimePasswords\Enums\ConsumeOneTimePasswordResult;
// $result is an instance of the ConsumeOneTimePasswordResult enum.
$result = $user->attemptLoginUsingOneTimePassword($oneTimePassword, remember: false);
if ($result->isOk()) {
// it is best practice to regenerate the session id after a login
$request->session()->regenerate();
return redirect()->intended('dashboard');
}
return back()->withErrors([
'one_time_password' => $result->validationMessage(),
])->onlyInput('one_time_password');
Alternatively, you can use the consumeOneTimePassword
. Which will do the same as attemptLoginUsingOneTimePassword
except it won't log in the user.
$result = $user->consumeOneTimePassword($oneTimePassword);
This method will return an instance of the ConsumeOneTimePasswordResult
enum which has these cases:
Ok
: The one-time password was correct.NoOneTimePasswordsFound
: The user has no one-time passwords.IncorrectOneTimePassword
: The one-time password was incorrect.DifferentOrigin
: The one-time password was created from a different origin.OneTimePasswordExpired
: The one-time password has expired.RateLimitExceeded
: The user has exceeded the rate limit for one-time passwords.
Using the Livewire component
The package provides a Livewire component that can create and consume one-time passwords. Here's how you can use it in your view.
<livewire:one-time-password>
It will render a form asking for the user's email address and a button to send the one-time password. After the user submits the form, it will send a one-time password to the provided email address.
The component will then display a form asking for the one-time password and a button to verify it. After the user submits the form, it will verify the one-time password and log the user in if the verification is successful.
Sending one-time passwords via SMS
The package uses a notification class that you can be extend to be used in any notification channel you want. Let’s see how we can add SMS support.
In the Laravel docs, you can read how to add support for SMS via Vonage to your app, make sure to read that first.
To add support for SMS for one-time passwords, start by creating a new notification class that extends the Spatie\OneTimePasswords\Notifications\OneTimePasswordNotification
class and adding the necessary methods.
namespace App\Notifications;
use Spatie\OneTimePasswords\Notifications\OneTimePasswordNotification;
class CustomOneTimePasswordNotification extends OneTimePasswordNotification
{
public function via($notifiable): string|array
{
return ['vonage']);
}
public function toVonage(object $notifiable): VonageMessage
{
// $this->oneTimePassword is an instance of the Spatie\OneTimePasswords\OneTimePassword model
return (new VonageMessage)
->content("Your one-time login code is: {$this->oneTimePassword->password}");
}
}
Don't for get to add the routeNotificationForVonage
to your User
model (as explained in the Laravel docs).
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;
class User extends Authenticatable
{
use Notifiable;
/**
* Route notifications for the Vonage channel.
*/
public function routeNotificationForVonage(Notification $notification): string
{
return $this->phone_number;
}
}
Finally, you need to update the config/one-time-passwords.php
configuration file to use your custom notification class.
// config/one-time passwords.php
return [
// ...
'notification' => => App\Notifications\CustomOneTimePasswordNotification::class
];
And with this in place, one-time passwords will be delivered via SMS. Pretty neat!
In closing
Our new package gives you everything you need to implement secure, user-friendly one-time password authentication in your Laravel applications. Whether you need a simple way to verify user actions or want to build a complete passwordless login system, this package provides the tools to make it happen.
You can read more on the feature and usage in our extensive documentation.
If you enjoy this package, check out our other Laravel packages like laravel-permission for managing user permissions, laravel-medialibrary for file uploads, and laravel-backup for handling application backups. Check out our complete list of packages.
You can support our open source efforts by purchasing one of our paid offerings, or by subscribing to Flare or Mailcoach.