mrpunyapal / php-2fa
Framework-agnostic Two-Factor Authentication actions for PHP with optional Laravel support
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/mrpunyapal/php-2fa
Requires
- php: ^8.3
- pragmarx/google2fa: ^9.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0|^9.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-type-coverage: ^3.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- rector/rector: ^2.0
- spatie/laravel-package-tools: ^1.16
Suggests
- illuminate/contracts: Required for Laravel integration (^11.0|^12.0)
- illuminate/support: Required for Laravel integration (^11.0|^12.0)
- spatie/laravel-package-tools: Required for Laravel integration (^1.16)
This package is auto-updated.
Last update: 2026-02-22 18:06:16 UTC
README
Framework-agnostic Two-Factor Authentication (TOTP) actions for PHP. Works with any authenticator app (Google Authenticator, Authy, etc.). Optional first-party Laravel support included.
Inspired by Laravel Fortify and built on top of pragmarx/google2fa.
Features
- Enable / Disable / Confirm 2FA
- Verify OTP codes
- Recovery code generation, verification, and regeneration
- Confirmable 2FA flow (user must verify a code before 2FA is active)
- Framework-agnostic core — use with any PHP application
- Optional Laravel integration with service provider, config, and Eloquent trait
- AES-256-CBC encryption out of the box (
OpenSslEncryptor) - Bring your own encryptor via the
Encryptorcontract
Requirements
- PHP 8.3+
- OpenSSL extension
Installation
composer require mrpunyapal/php-2fa
Laravel
The service provider is auto-discovered. Publish the config:
php artisan vendor:publish --tag="two-factor-config"
Quick Start (Vanilla PHP)
1. Implement TwoFactorUser on your user entity
use MrPunyapal\Php2fa\Contracts\TwoFactorUser; use DateTimeImmutable; class User implements TwoFactorUser { private ?string $twoFactorSecret = null; private ?string $twoFactorRecoveryCodes = null; private ?DateTimeImmutable $twoFactorConfirmedAt = null; public function getTwoFactorSecret(): ?string { return $this->twoFactorSecret; } public function setTwoFactorSecret(?string $secret): void { $this->twoFactorSecret = $secret; // persist to database } public function getTwoFactorRecoveryCodes(): ?string { return $this->twoFactorRecoveryCodes; } public function setTwoFactorRecoveryCodes(?string $codes): void { $this->twoFactorRecoveryCodes = $codes; // persist to database } public function getTwoFactorConfirmedAt(): ?DateTimeImmutable { return $this->twoFactorConfirmedAt; } public function setTwoFactorConfirmedAt(?DateTimeImmutable $confirmedAt): void { $this->twoFactorConfirmedAt = $confirmedAt; // persist to database } }
2. Use TwoFactorManager
use MrPunyapal\Php2fa\TwoFactorManager; $manager = TwoFactorManager::create( issuer: 'My App', encryptionKey: 'your-secret-encryption-key', ); // Enable 2FA $setup = $manager->enable($user, 'user@example.com'); // $setup->secret — plain text secret (show once) // $setup->qrCodeUrl — otpauth:// URL (render as QR code) // $setup->recoveryCodes — array of recovery codes (show once) // Confirm 2FA (user enters code from authenticator app) $manager->confirm($user, $otpCode); // Verify OTP or recovery code during login $valid = $manager->verify($user, $code); // Regenerate recovery codes $newCodes = $manager->regenerateRecoveryCodes($user); // Disable 2FA $manager->disable($user);
Using Individual Actions
If you prefer dependency injection or want granular control:
use MrPunyapal\Php2fa\Actions\EnableTwoFactorAuthentication; use MrPunyapal\Php2fa\Actions\ConfirmTwoFactorAuthentication; use MrPunyapal\Php2fa\Actions\VerifyTwoFactorCode; use MrPunyapal\Php2fa\Actions\DisableTwoFactorAuthentication; use MrPunyapal\Php2fa\Actions\GenerateRecoveryCodes; use MrPunyapal\Php2fa\Services\TwoFactorService; use MrPunyapal\Php2fa\Support\OpenSslEncryptor; $service = new TwoFactorService(issuer: 'My App'); $encryptor = new OpenSslEncryptor('your-secret-key'); $enable = new EnableTwoFactorAuthentication($service, $encryptor); $setup = $enable($user, 'user@example.com'); $confirm = new ConfirmTwoFactorAuthentication($service, $encryptor); $confirm($user, $otpCode); $verify = new VerifyTwoFactorCode($service, $encryptor); $isValid = $verify($user, $code); $regenerate = new GenerateRecoveryCodes($encryptor); $codes = $regenerate($user); $disable = new DisableTwoFactorAuthentication(); $disable($user);
Laravel Usage
Add the trait to your User model
use MrPunyapal\Php2fa\Contracts\TwoFactorUser; use MrPunyapal\Php2fa\Laravel\Concerns\HasTwoFactorAuthentication; class User extends Authenticatable implements TwoFactorUser { use HasTwoFactorAuthentication; }
Add the required columns
Schema::table('users', function (Blueprint $table) { $table->text('two_factor_secret')->nullable(); $table->text('two_factor_recovery_codes')->nullable(); $table->timestamp('two_factor_confirmed_at')->nullable(); });
Inject actions or manager
use MrPunyapal\Php2fa\Actions\EnableTwoFactorAuthentication; class TwoFactorController extends Controller { public function store( Request $request, EnableTwoFactorAuthentication $enable, ) { $setup = $enable($request->user(), $request->user()->email); return response()->json([ 'qr_code_url' => $setup->qrCodeUrl, 'recovery_codes' => $setup->recoveryCodes, ]); } }
Configuration
// config/two-factor.php return [ 'issuer' => env('TWO_FACTOR_ISSUER', config('app.name', 'My App')), 'secret_length' => (int) env('TWO_FACTOR_SECRET_LENGTH', 32), 'window' => (int) env('TWO_FACTOR_WINDOW', 1), 'algorithm' => env('TWO_FACTOR_ALGORITHM', 'sha1'), // sha1, sha256, sha512 'recovery_code_count' => (int) env('TWO_FACTOR_RECOVERY_CODE_COUNT', 8), 'confirmable' => (bool) env('TWO_FACTOR_CONFIRMABLE', true), ];
Custom Encryptor
Implement the Encryptor contract to use your own encryption strategy:
use MrPunyapal\Php2fa\Contracts\Encryptor; class MyEncryptor implements Encryptor { public function encrypt(string $value): string { // your encryption logic } public function decrypt(string $value): string { // your decryption logic } }
Then pass it to the actions or bind it in Laravel's container.
API Reference
Actions
| Action | Purpose |
|---|---|
EnableTwoFactorAuthentication |
Generates secret + recovery codes, stores encrypted on user |
DisableTwoFactorAuthentication |
Clears all 2FA fields on user |
ConfirmTwoFactorAuthentication |
Verifies OTP code and sets confirmed timestamp |
VerifyTwoFactorCode |
Verifies OTP or recovery code, replaces used recovery codes |
GenerateRecoveryCodes |
Generates new set of recovery codes |
Exceptions
| Exception | When |
|---|---|
InvalidOtpException |
OTP code verification fails during confirmation |
TwoFactorNotEnabledException |
Action requires 2FA to be enabled but it isn't |
TwoFactorAlreadyEnabledException |
2FA is already confirmed and active |
EncryptionException |
Encryption or decryption operation fails |
Testing
composer test
Credits
License
The MIT License (MIT). Please see License File for more information.