PHPackages                             empuxa/laravel-totp-login - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. empuxa/laravel-totp-login

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

empuxa/laravel-totp-login
=========================

Say goodbye to passwords and sign in via TOTP instead.

v7.1.0(2mo ago)210.1k↓66.7%MITPHPPHP ^8.2CI passing

Since Jun 26Pushed 2mo agoCompare

[ Source](https://github.com/empuxa/laravel-totp-login)[ Packagist](https://packagist.org/packages/empuxa/laravel-totp-login)[ Docs](https://github.com/empuxa/laravel-totp-login)[ GitHub Sponsors](https://github.com/empuxa)[ RSS](/packages/empuxa-laravel-totp-login/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (22)Versions (27)Used By (0)

Laravel TOTP Login
==================

[](#laravel-totp-login)

[![Latest Version on Packagist](https://camo.githubusercontent.com/78942ca58600580568ff79ff38b6661ec4ef6763638d2f5d63807e69f46653b7/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f656d707578612f6c61726176656c2d746f74702d6c6f67696e2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/empuxa/laravel-totp-login)[![Tests](https://camo.githubusercontent.com/b2f596836c23049bce96c4e454348f84ff5b1fd857a3cc01add21ffeb03a6fe0/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f656d707578612f6c61726176656c2d746f74702d6c6f67696e2f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/empuxa/laravel-totp-login/actions/workflows/run-tests.yml)[![Total Downloads](https://camo.githubusercontent.com/97dd1bdc51e982629505bc4f5c3d2fc50792f9bca303f4b5ca013d1b56bb6b72/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f656d707578612f6c61726176656c2d746f74702d6c6f67696e2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/empuxa/laravel-totp-login)

[![Banner](https://camo.githubusercontent.com/1e37ba15f2c560bbe279bd5919d090e4d3e01f6151b5fd23c3a39c597cbdae7d/68747470733a2f2f62616e6e6572732e6265796f6e64636f2e64652f4c61726176656c253230544f54502532304c6f67696e2e706e673f7468656d653d6c69676874267061636b6167654d616e616765723d636f6d706f7365722b72657175697265267061636b6167654e616d653d656d707578612532466c61726176656c2d746f74702d6c6f67696e267061747465726e3d617263686974656374267374796c653d7374796c655f31266465736372697074696f6e3d476f6f646279652b70617373776f726473253231266d643d312673686f7757617465726d61726b3d3026666f6e7453697a653d313030707826696d616765733d68747470732533412532462532466c61726176656c2e636f6d253246696d672532466c6f676f6d61726b2e6d696e2e737667)](https://camo.githubusercontent.com/1e37ba15f2c560bbe279bd5919d090e4d3e01f6151b5fd23c3a39c597cbdae7d/68747470733a2f2f62616e6e6572732e6265796f6e64636f2e64652f4c61726176656c253230544f54502532304c6f67696e2e706e673f7468656d653d6c69676874267061636b6167654d616e616765723d636f6d706f7365722b72657175697265267061636b6167654e616d653d656d707578612532466c61726176656c2d746f74702d6c6f67696e267061747465726e3d617263686974656374267374796c653d7374796c655f31266465736372697074696f6e3d476f6f646279652b70617373776f726473253231266d643d312673686f7757617465726d61726b3d3026666f6e7453697a653d313030707826696d616765733d68747470732533412532462532466c61726176656c2e636f6d253246696d672532466c6f676f6d61726b2e6d696e2e737667)

Say goodbye to passwords and sign in via a time-based one-time password instead! Laravel TOTP Login is a convenient package that allows you to easily add a TOTP login feature to your Laravel application.

Why Choose Laravel TOTP Login?
------------------------------

[](#why-choose-laravel-totp-login)

You might wonder why you should opt for a TOTP login instead of a magic link solution. Well, this package is designed to complement the existing login methods in your application. It provides an alternative sign-in option for users who haven't set a password yet or don't have an email address. For instance, users who signed up with only a phone number can still enjoy the benefits of secure login through a TOTP.

Features
--------

[](#features)

- Simplified sign-in process using a TOTP
- Compatibility with existing login methods
- Support for users without passwords or email addresses
- Built-in security protections:
    - Rate limiting with progressive event tracking
    - Timing attack prevention in code validation
    - Race condition prevention with database locking
    - Session fixation protection

[![How it works](docs/animation.gif)](docs/animation.gif)

Requirements
------------

[](#requirements)

In addition to Laravel v9.52 or newer, this package relies on [Alpine.js](https://alpinejs.dev/). If you're using [Laravel LiveWire](https://laravel-livewire.com/), you are already good to go. Otherwise, ensure to include Alpine.js in your application. Also, you need to have a notifiable user model.

Installation
------------

[](#installation)

Install the package via composer:

```
composer require empuxa/laravel-totp-login
```

Copy the vendor files and adjust the config file `config/totp-login.php` to your needs:

```
php artisan vendor:publish --provider="Empuxa\TotpLogin\TotpLoginServiceProvider"
```

Adjust the config to your needs, then run the migrations:

```
php artisan migrate
```

That's it! You're ready to start using the TOTP login feature in your Laravel application.

Configuration
-------------

[](#configuration)

The package offers extensive configuration options in `config/totp-login.php`. Key settings include:

- **Rate limiting**: Configure max attempts and throttling behavior
- **Code settings**: Customize code length, expiration time, and validation rules
- **User model**: Specify your user model and identifier column (email, phone, etc.)
- **Notification**: Choose custom notification classes for different channels
- **Events**: Replace default event classes with custom implementations
- **Routes**: Customize route prefix and middleware

See the published [config file](/config/totp-login.php) for detailed explanations of all available options.

Usage
-----

[](#usage)

The sign-in process for this repository involves three steps:

1. Enter the user's email address, phone number, or any other specified identifier, and request a TOTP.
2. If the entered information is valid, a TOTP will be sent to the user. You may need to customize the notification channel based on the user model you are using.
3. Enter the received TOTP to log in the user.

### Routes

[](#routes)

By default, the package registers the following routes under the `/login` prefix (you can change it in the config):

- `GET /login` - Show identifier entry form
- `POST /login` - Handle identifier submission and send TOTP
- `GET /login/code` - Show code entry form
- `POST /login/code` - Handle code verification and authenticate user

#### Customizing Routes

[](#customizing-routes)

You can customize the route prefix in `config/totp-login.php`:

```
'route' => [
    'prefix' => 'auth', // Changes routes to /auth, /auth/code, etc.
    'middleware' => ['web', 'guest'],
],
```

#### Manual Route Registration

[](#manual-route-registration)

If you need more control, you can disable automatic route registration and register routes manually in your `routes/web.php`:

```
use Empuxa\TotpLogin\Controllers\HandleCodeRequest;
use Empuxa\TotpLogin\Controllers\HandleIdentifierRequest;
use Empuxa\TotpLogin\Controllers\ShowCodeForm;
use Empuxa\TotpLogin\Controllers\ShowIdentifierForm;

Route::prefix('auth')->group(static function (): void {
    Route::get('/login', ShowIdentifierForm::class)->name('totp-login.identifier.form');
    Route::post('/login', HandleIdentifierRequest::class)->name('totp-login.identifier.handle');
    Route::get('/login/code', ShowCodeForm::class)->name('totp-login.code.form');
    Route::post('/login/code', HandleCodeRequest::class)->name('totp-login.code.handle');
});
```

### Using Custom Identifiers

[](#using-custom-identifiers)

By default, the package uses email addresses as identifiers. However, you can use any column from your user model (phone numbers, usernames, etc.):

```
// config/totp-login.php
'columns' => [
    'identifier' => 'phone', // Use phone number instead of email
],
```

Make sure to update your validation rules accordingly:

```
'identifier' => [
    'validation' => 'required|string|regex:/^\+[1-9]\d{1,14}$/', // E.164 phone format
],
```

Don't forget to update the notification afterward to send SMS instead of mails!

### Superpin for Testing

[](#superpin-for-testing)

During development and testing, you can enable a "superpin" that works for all users. While the superpin is always valid, the package still dispatches the notification, so you can use either the superpin or the actual code sent to the user for login.

```
TOTP_LOGIN_SUPERPIN=123456
```

**Important**: Superpins are automatically disabled in production environments and only work in environments specified in your config (default: `local`, `testing`). You can also specify individual user identifiers that can bypass environment restrictions for staging/demo purposes.

See `config/totp-login.php` for more superpin configuration options.

### Customizing the Views

[](#customizing-the-views)

While the initial steps are relatively straightforward, it's now necessary to customize the views. These views have been designed to be as simple as possible (some might even consider them "ugly") and can be located in the `resources/views/vendor/totp-login` directory.

*Why are they not visually appealing?*Different applications adopt various layouts and frameworks. Since you have the most knowledge about your application, you can change the views to suit your specific requirements.

### Modifying the Notification

[](#modifying-the-notification)

The package publishes a default notification view at `resources/views/vendor/totp-login/notification.blade.php`. You may want to make adjustments to this notification to align it with your preferences and needs.

#### Different Notification Channels

[](#different-notification-channels)

If you plan on using SMS or similar as your preferred notification channel, you can create a custom notification class. The TOTP and the user's IP address will be passed to the constructor of this class. Finally, replace the default notification class within the `config/totp-login.php` file with your custom notification.

### Custom User Model Scope

[](#custom-user-model-scope)

By default, the package looks up users without any additional filtering. However, you might need to restrict which users can use TOTP login. Common use cases include:

- Only allowing users with verified email addresses
- Excluding deleted or suspended accounts
- Filtering by user type or role (e.g., only customers, not administrators)
- Applying multi-tenancy restrictions

To apply a scope to your user model, add the `totpLoginScope()` method to your User model:

```
public static function totpLoginScope(): Builder
{
    return self::where('email_verified_at', '!=', null)
               ->where('status', 'active');
}
```

For example, if you're using soft deletes and want to exclude trashed users:

```
public static function totpLoginScope(): Builder
{
    return self::withoutTrashed();
}
```

Or if you have a multi-tenant application:

```
public static function totpLoginScope(): Builder
{
    return self::where('tenant_id', session('tenant_id'));
}
```

Events
------

[](#events)

The package dispatches various events throughout the authentication process, allowing you to monitor and respond to authentication attempts, failures, and rate limiting violations.

### Success Events

[](#success-events)

- **`LoginRequestViaTotp`** - Fired when a user successfully requests a TOTP code
- **`LoggedInViaTotp`** - Fired when a user successfully authenticates with a TOTP code

### Failure Events

[](#failure-events)

#### Identifier Phase

[](#identifier-phase)

- **`InvalidIdentifierFormat`** - Invalid identifier format (e.g., invalid email)
- **`UserNotFound`** - Valid format but user doesn't exist
- **`IdentifierRateLimitExceeded`** - First time hitting identifier rate limit
- **`IdentifierRateLimitContinued`** - Continued attempts after identifier rate limit hit

#### Code Phase

[](#code-phase)

- **`MissingSessionInformation`** - Session expired or missing
- **`MissingCodeData`** - Code data not properly submitted
- **`InvalidCodeFormat`** - Invalid code format or length
- **`CodeExpired`** - Valid code but expired
- **`IncorrectCode`** - Wrong code entered
- **`CodeRateLimitExceeded`** - First time hitting code rate limit
- **`CodeRateLimitContinued`** - Continued attempts after code rate limit hit

### Rate Limit Events

[](#rate-limit-events)

- **`Lockout`** (Laravel's core event) - Fired alongside `*RateLimitExceeded` events to follow Laravel's conventions and allow integration with existing Laravel authentication listeners

### Rate Limit Event Behavior

[](#rate-limit-event-behavior)

The package distinguishes between initial rate limit violations and persistent abuse:

1. **First rate limit hit**: Fires `CodeRateLimitExceeded` or `IdentifierRateLimitExceeded`(package-specific) + `Lockout` (Laravel's standard event for rate limiting)
2. **Subsequent attempts**: Fires `CodeRateLimitContinued` or `IdentifierRateLimitContinued`on each attempt (no `Lockout` event)

This allows you to:

- Monitor initial rate limit violations
- Detect persistent brute force attacks
- Implement progressive security measures (e.g., IP blocking)

### Listening to Events

[](#listening-to-events)

#### Using Event Subscriber (Recommended)

[](#using-event-subscriber-recommended)

The recommended approach is to use an event subscriber with config keys. This way, if you customize the event classes in your config, your listeners will automatically use the correct events:

```
namespace App\Listeners;

class TotpLoginEventSubscriber
{
    public function subscribe(): array
    {
        return [
            config('totp-login.events.login_request_via_totp') => [],
            config('totp-login.events.logged_in_via_totp') => [
                LogLoginEvent::class,
            ],
            config('totp-login.events.code_rate_limit_exceeded') => [
                LogRateLimitViolation::class,
            ],
            config('totp-login.events.code_rate_limit_continued') => [
                AlertSecurityTeam::class,
                BlockSuspiciousIP::class,
            ],
            config('totp-login.events.identifier_rate_limit_exceeded') => [
                LogRateLimitViolation::class,
            ],
            config('totp-login.events.identifier_rate_limit_continued') => [
                AlertSecurityTeam::class,
                BlockSuspiciousIP::class,
            ],
        ];
    }
}
```

Register the subscriber in your `EventServiceProvider`:

```
use App\Listeners\TotpLoginEventSubscriber;

protected $subscribe = [
    TotpLoginEventSubscriber::class,
];
```

#### Using Direct Event Classes

[](#using-direct-event-classes)

Alternatively, you can listen to events directly in your `EventServiceProvider`:

```
use Empuxa\TotpLogin\Events\CodeRateLimitExceeded;
use Empuxa\TotpLogin\Events\CodeRateLimitContinued;

protected $listen = [
    CodeRateLimitExceeded::class => [
        LogRateLimitViolation::class,
    ],
    CodeRateLimitContinued::class => [
        AlertSecurityTeam::class,
        BlockSuspiciousIP::class,
    ],
];
```

### Customizing Events

[](#customizing-events)

All events are configurable in `config/totp-login.php` under the `events` key. You can replace the default event classes with your own custom implementations:

```
// config/totp-login.php
'events' => [
    'code_rate_limit_exceeded' => \App\Events\CustomCodeRateLimitExceeded::class,
    'code_rate_limit_continued' => \App\Events\CustomCodeRateLimitContinued::class,
    'identifier_rate_limit_exceeded' => \App\Events\CustomIdentifierRateLimitExceeded::class,
    'identifier_rate_limit_continued' => \App\Events\CustomIdentifierRateLimitContinued::class,
    // ... other events
],
```

When using the event subscriber approach with config keys (recommended), your listeners will automatically use these custom event classes without any changes to your subscriber.

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Marco Raddatz](https://github.com/marcoraddatz)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

51

—

FairBetter than 96% of packages

Maintenance85

Actively maintained with recent releases

Popularity26

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity67

Established project with proven stability

 Bus Factor1

Top contributor holds 80.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~42 days

Recently: every ~67 days

Total

24

Last Release

80d ago

Major Versions

v2.0.2 → v3.0.02024-03-15

v3.0.1 → v4.0.0-rc12024-04-19

v4.0.0 → v5.0.02024-09-17

v5.0.0 → v6.0.02024-10-25

v6.3.0 → v7.0.0-rc.12025-10-31

PHP version history (2 changes)v1.0.0PHP ^8.1

v6.3.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/9a2715a7b4e17b0ed07d23b300474f34d97d7abafbb602ec399f352a62f2e527?d=identicon)[marcoraddatz](/maintainers/marcoraddatz)

---

Top Contributors

[![marcoraddatz](https://avatars.githubusercontent.com/u/248815?v=4)](https://github.com/marcoraddatz "marcoraddatz (135 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (21 commits)")[![StyleCIBot](https://avatars.githubusercontent.com/u/11048387?v=4)](https://github.com/StyleCIBot "StyleCIBot (7 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (4 commits)")

---

Tags

Passwordlessempuxatotp-login

###  Code Quality

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/empuxa-laravel-totp-login/health.svg)

```
[![Health](https://phpackages.com/badges/empuxa-laravel-totp-login/health.svg)](https://phpackages.com/packages/empuxa-laravel-totp-login)
```

###  Alternatives

[spatie/laravel-backup

A Laravel package to backup your application

6.0k21.8M191](/packages/spatie-laravel-backup)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k25.9M107](/packages/laravel-cashier)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k12.1M99](/packages/laravel-pulse)[bezhansalleh/filament-shield

Filament support for `spatie/laravel-permission`.

2.8k2.9M88](/packages/bezhansalleh-filament-shield)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[psalm/plugin-laravel

Psalm plugin for Laravel

3274.9M308](/packages/psalm-plugin-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
