roundly-consulting/two-factor-for-laravel
A Laravel package for adding two-factor authentication with TOTP, QR-code setup, recovery codes, and middleware protection. Simple configuration and Blade-ready flows help secure user accounts without building 2FA from scratch.
composer require roundly-consulting/two-factor-for-laravel — the service provider is auto-discovered, so there is nothing to register by hand in a standard Laravel app.
Look first at the package service provider: it is where $table->twoFactorColumns() and $table->dropTwoFactorColumns() are registered as Blueprint macros, and where the config/published assets (if any) come from. If something "doesn't exist", that provider is almost certainly not booted.
First use case: add the two-factor columns to your users table in a normal migration:
Schema::table('users', function (Blueprint $table) {
$table->twoFactorColumns();
});
Then apply the package's trait/contract to your User model (per the package README) so the columns are actually read and written by the 2FA flow.
If you run Larastan/PHPStan, the 1.1.6 release removes the "Call to an undefined method" noise for these macros. With phpstan/extension-installer installed, the shipped extension.neon loads automatically via extra.phpstan.includes. Without it, add the include yourself:
includes:
- vendor/roundly-consulting/two-factor-for-laravel/extension.neon
Greenfield tables. Call $table->twoFactorColumns() inside Schema::create() rather than hand-writing the secret/recovery-code/confirmed-at columns. One call keeps the schema and the package's expectations in sync across every table that supports 2FA (e.g. users and an admin table).
Adding 2FA to an existing table. Use a dedicated Schema::table() migration, and keep dropTwoFactorColumns() in down() only if you genuinely want the rollback to be destructive:
public function up(): void
{
Schema::table('users', fn (Blueprint $table) => $table->twoFactorColumns());
}
public function down(): void
{
Schema::table('users', fn (Blueprint $table) => $table->dropTwoFactorColumns());
}
Multiple authenticatable models. Because the macros are table-agnostic, the same pattern works for admins, members, etc. Extract a small helper or a shared migration stub if you have more than two such tables.
Package/Testbench development. When you are writing a package that itself calls these macros, the host app's provider is not booted during analysis. Rely on the shipped extension.neon rather than expecting Larastan to discover the macro through a real application boot.
CI wiring. Treat static analysis as part of the 2FA feature work: run Larastan in CI with the extension included, so a future migration that calls these macros fails the build only for real reasons, not for missing stubs.
Manual provider registration. If your app uses dont-discover or registers providers by hand (e.g. inside a module/service provider list), the macros are still fine at runtime as long as the provider boots — but PHPStan will need the explicit extension.neon include.
migrate), but static analysis, Testbench package test suites, and dont-discover setups are where the "undefined method" error appears. The 1.1.6 extension is the fix — don't "fix" it by replacing the macro with hand-written columns.phpstan/extension-installer must be allowed. On Composer 2.2+, confirm config.allow-plugins includes phpstan/extension-installer, otherwise the extension silently does not load and you will see the same error you thought you fixed.extra.phpstan.includes is not magic. It is only consumed by the installer. If you use a hand-rolled phpstan.neon, a Neon include from another path, or a monorepo with a root-level config, add the vendor/.../extension.neon include explicitly and check the path is correct relative to the config file.dropTwoFactorColumns() is destructive. Rolling back wipes secrets and recovery codes, locking out every enrolled user. On production, prefer a forward-only migration that drops the columns after a deliberate deprecation window, or override down() to a no-op.php artisan about to confirm the provider is loaded; check bootstrap/providers.php/config/app.php if discovery is disabled; vendor/bin/phpstan analyse --debug to confirm the extension is parsed; clear config cache after changing provider registration.How can I help you explore Laravel packages today?