Installation
Require the package, then publish and run the migration. The service provider and the TwoFactor facade alias are auto-discovered — no manual registration:
composer require roundly-consulting/two-factor-for-laravel
php artisan vendor:publish --tag="two-factor-migrations"
php artisan migrateMigrations are publish-only — nothing is auto-loaded from the package. The migration stamps its own timestamp on publish, and re-publishing overwrites the file it published last time, so you never end up with two copies of the same migration.
Forward-only and safe to re-run
The published migration has no down(), so migrate:rollback leaves the four columns in place. Running it again is safe — it adds only the columns the table doesn’t have yet, so migrate after a rollback and migrate:refresh never fail on a duplicate column. To remove the columns, drop them in a migration of your own:
Schema::table('users', function (Blueprint $table): void { // or your `two-factor.table`
$table->dropTwoFactorColumns();
});What the migration adds
Four nullable columns on your users table — or on the table named by two-factor.table (TWO_FACTOR_TABLE), read when the migration runs. If you write your own migration instead, use the Blueprint macro:
Schema::table('users', function (Blueprint $table): void {
$table->twoFactorColumns(); // secret, recovery_codes, confirmed_at, last_used_timestep
});Other account tables
Two-factor works on any Eloquent model that uses the trait — clients, admins, one per guard. The published migration covers one table; give every further account table the same columns in your own migration. The dropTwoFactorColumns() macro reverses it:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('clients', function (Blueprint $table): void {
$table->twoFactorColumns();
});
}
public function down(): void
{
Schema::table('clients', function (Blueprint $table): void {
$table->dropTwoFactorColumns();
});
}
};Column names come from the shared two-factor.columns map, and the built-in limiter keys on the model class as well as the id, so a user and a client with the same id never share a lockout.
Config (optional)
The package works with zero configuration — every key has a safe default. Publish the config only to override:
php artisan vendor:publish --tag="two-factor-config"The two publish tags are two-factor-migrations and two-factor-config. Next, prepare your user model — see Model setup.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.