Download the PHP package thecyrilcril/laravel-otp without Composer

On this page you can find all versions of the php package thecyrilcril/laravel-otp. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-otp

Laravel OTP

Purpose-scoped, hashed, rate-limited one-time passwords for Laravel.

Tests Latest Version License

What it is

This package adds one-time-password (OTP) verification to any Eloquent model. An OTP is a short numeric code — the kind sent to verify an email address, a phone number, or a login attempt.

Four properties make it safe to use in production:

  1. Purpose-scoped. Every code is issued for a specific purpose (EmailVerification, PasswordReset, and so on). A code issued for one purpose can never satisfy a check for a different purpose, even if the digits happen to match.
  2. Hashed at rest. Codes are never stored as plain text — they're stored as bcrypt hashes, the same one-way scrambling used for passwords. A leaked database yields no usable codes.
  3. Rate-limited on both ends. Issuing new codes is throttled (so nobody can spam a victim's inbox), and verifying codes is throttled separately (so nobody can brute-force a guess).
  4. Single-use. Once a code is successfully consumed, it's deleted. It can never be replayed.

The package was extracted from two hand-rolled OTP implementations that had been running in production without these controls, and hardened with what both were missing: bcrypt storage instead of reversible encryption, and rate limiting on top of a bare attempts counter.

One thing it deliberately does not do: send anything. issueOtp() hands you the plaintext code exactly once. You write your own notification class to email it, text it, or deliver it however you like — see the example further down for exactly how to wire that up.

It exists because nothing else on Packagist covers all three of purpose-scoping, hashing, and rate limiting at once: spatie/laravel-one-time-passwords stores codes in plaintext with no purpose scoping, otpz has no concept of purposes, and otpify has no rate limiting.

Install

The published migration is forward-only — it has no down() method — so php artisan migrate:rollback will not drop the otps table.

The config publish is optional. Every setting has a sensible default; see Configuration below. Publish it only if you need to change a limit, the expiry window, or the code length.

Setup

Two steps.

1. Define a purpose enum implementing OtpPurpose. This is what scopes every code to what it's actually for:

2. Add the HasOtps trait to whichever model will issue and verify codes — typically your User model, but any Eloquent model works, since the relationship is polymorphic:

The enum's value() is stored in the purpose column and scopes every operation — a code issued for one purpose can never satisfy a check against another.

Usage

Issuing a code

issueOtp() never sends anything. It only creates the code and hands it back to you. Feed $issued->code into your own notification — see the worked example below.

The package's involvement ends the moment it returns the code. That code is not stored anywhere in plaintext, and IssuedOtp masks it in both debug output and JSON serialization — so neither a stray dump($issued) nor a JSON-normalizing logger (Log::info('...', ['otp' => $issued])) will leak it.

Issuing a new code for a purpose deletes any existing, unconsumed code for that same purpose first. There is only ever one live code per model+purpose pair.

Verifying vs. consuming

Both return bool.

Every attempt — right or wrong — counts against the rate limiter. A successful attempt is refunded a single unit, so legitimate users are net-zero. Every failed attempt additionally charges the per-code attempts budget. verifyOtp() is non-destructive on success only — it is not a free-to-guess channel.

Context binding

Pass context when issuing and again when verifying to bind a code to the value it was sent to:

This closes a change-of-target hole. Without it, a user could request a code for phone A, edit the pending phone number to B, then submit the code they received on A — and B would end up verified having never received anything. Binding the code to the address it was actually sent to means a code sent to A only ever satisfies a check against A.

Omit context for purposes with no target to bind, such as a login-time 2FA check:

Handling throttling

Both issuing and verifying are rate-limited. Either can throw:

Sending the code: a worked example

The package hands you the plaintext code once. It never sends it. Here is a complete, copy-pasteable notification you can drop into your project and adjust:

Use it right after issuing a code:

Two details in that class matter beyond boilerplate:

Swap via()/toMail() for your SMS provider's channel if you're sending by text instead — the ShouldQueue + ShouldBeEncrypted pair still applies either way.

Security model

Control Detail
Hashed at rest Codes are stored via Eloquent's hashed (bcrypt) cast — a leaked database yields no usable codes.
Verification order Rate limit → row lookup → expiry → Hash::check → context hash_equals. The limiter runs first, so a brute-forcer cannot even trigger bcrypt work once throttled.
Both-ends rate limiting Issuing is throttled (stops mail/SMS bombing and DoS-by-regeneration against a victim); verifying is throttled separately (stops brute force). Both limiter gates are atomic — a burst of concurrent requests cannot slip through a check-then-hit gap.
Per-code attempts budget A per-row failure counter kills a code outright after too many wrong guesses, even if the rate-limit window has already rolled over.
Single-use, lock-free consumeOtp() uses compare-and-delete (WHERE id = ? AND attempts = ?, checking affected === 1) rather than a row lock — two simultaneous submissions of the same code cannot both succeed, and no database connection is pinned while the bcrypt check runs.
Enumeration-resistant Every failure path — missing row, expired, wrong code, wrong context, throttled — returns the same false to the caller, at the same cost (response timing is equalized across every branch). The precise reason is only ever visible internally, via the failure event.
Context binding Optional target binding (see above) closes the change-of-target attack that plain code verification is otherwise silent to.
Secrets hygiene Code parameters are marked #[SensitiveParameter] throughout; IssuedOtp masks the code in __debugInfo() and jsonSerialize(), and throws if you try to serialize() it (that is how queue payloads and file/database sessions encode objects — the one channel that would write the plaintext to durable storage).

⚠️ Do not call verifyOtp()/consumeOtp() inside a transaction that rolls back

A failed check charges two budgets: the rate limiter (in the cache) and the per-code attempts counter (a database row). The package deliberately runs the attempts write outside any transaction of its own — but it cannot escape a transaction you opened. If your code wraps the call and then throws to signal failure, your rollback takes the attempts increment with it, and an attacker gets unlimited guesses against a live code for its whole validity window.

The rate limiter is unaffected either way — it lives in the cache, not the database — so throttling still applies. But the per-code budget is the backstop that survives a flushed or per-server cache, and it's worth keeping intact.

Events

Three events are dispatched over the lifecycle, none of which ever carry a plaintext code:

The failure reason is only ever available to your own listeners. The boolean the caller gets back from verifyOtp()/consumeOtp() never reveals which of these it was, to avoid giving an attacker a signal to enumerate against.

Configuration

Publish the config with php artisan vendor:publish --tag=otp-config to override any of these defaults:

length is bounds-checked at runtime. Configuring fewer than 6 digits throws an InvalidArgumentException, because a shorter numeric code is brute-forceable even through the rate limiter. Configuring more than 10 digits throws for the same reason in reverse — there's no security benefit past 10, and it's almost certainly a misconfiguration.

Scheduling cleanup

Expired codes are cleaned up via Laravel's MassPrunable. Schedule the prune command:

Migration guide sketch

If you're migrating off a hand-rolled OTP trait shaped like the ones this package was extracted from:

Versioning note

This package is on 0.x until the first real consumer migration proves the API surface in production. Breaking changes are possible before 1.0.

License

The MIT License (MIT). See LICENSE.md.


All versions of laravel-otp with dependencies

PHP Build Version
Package Version
Requires php Version ^8.4
illuminate/contracts Version ^12.0|^13.0
illuminate/database Version ^12.0|^13.0
illuminate/support Version ^12.0|^13.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package thecyrilcril/laravel-otp contains the following files

Loading the files please wait ...