omaacha / 2fa
Multi-Factor Authentication (TOTP, Multi-Channel OTP, Recovery Codes, Trusted Devices, Sudo Mode) for Laravel APIs
Requires
- php: ^8.1|^8.2|^8.3
- illuminate/cache: ^10.0|^11.0
- illuminate/database: ^10.0|^11.0
- illuminate/http: ^10.0|^11.0
- illuminate/mail: ^10.0|^11.0
- illuminate/notifications: ^10.0|^11.0
- illuminate/support: ^10.0|^11.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Enterprise-grade, zero-dependency Multi-Factor Authentication (MFA / 2FA) engine for Laravel REST APIs and Web Applications.
π Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Route Protection & Sudo Mode
- Multi-Channel OTP Drivers
- API Endpoints Reference
- Configuration Reference
- Localization
- Testing
- Security
- License
β¨ Features
- Standard RFC 6238 TOTP Engine: Pure PHP implementation compatible with Microsoft Authenticator, Google Authenticator, 1Password, Authy, and Bitwarden.
- Pure Vector SVG QR Code: Built-in Nayuki-based QR Code generator. Zero external C-extensions required β no
ext-gdorext-imagickneeded. - Multi-Channel OTP Driver Architecture: Send one-time codes via Email, Laravel Notifications, SMS (Twilio / Vonage), or WhatsApp using the built-in driver manager.
- Sudo Mode Middleware (
2fa.sudo): Protect sensitive operations (e.g. deleting assets, altering user roles, exporting tenant data) by demanding fresh 2FA re-confirmation within a configurable window (15 mins). - Mandatory 2FA Policy Middleware (
2fa.enforce): Block access to critical route groups unless 2FA is activated on the user's account. - Hashed Single-Use Recovery Codes: Generates 10 cryptographically random, formatted recovery codes (
XXXX-XXXX) securely stored with SHA-256 hashes. - Trusted Devices ("Remember This Device"): Secure 14-day persistent HttpOnly cookie tokens bound to client fingerprints, with active device tracking, expiration, and user-initiated revocation.
- Anti-Enumeration Protection: Silent failure for invalid recovery/email requests to prevent email harvesting.
- 24-Hour Non-2FA Grace Window: Seamless fallback email verification with configurable grace period before re-prompting.
- Pre-Activation Ownership Verification: Forces verification via email before allowing a user to register an authenticator app.
- Agnostic Token Issuer: Decoupled from any specific auth library β works seamlessly with custom JWTs, Laravel Sanctum, Laravel Passport, or session guards.
- Multilingual Out of the Box: Shipped with complete translations for English, French, Spanish, and Arabic.
π Requirements
| Dependency | Supported Versions |
|---|---|
| PHP | ^8.1, ^8.2, ^8.3 |
| Laravel Framework | ^10.0, ^11.0 |
π Installation
1. Require via Composer
composer require omaacha/2fa
2. Publish Configuration & Migrations
# Publish configuration php artisan vendor:publish --tag=two-factor-config # Publish migration stubs php artisan vendor:publish --tag=two-factor-migrations # Run the database migrations php artisan migrate
π οΈ Quick Start
1. User Model Configuration
Implement TwoFactorAuthenticatable and include HasTwoFactorAuthentication in your authenticatable model (typically App\Models\User):
namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Omaacha\TwoFactor\Contracts\TwoFactorAuthenticatable; use Omaacha\TwoFactor\Traits\HasTwoFactorAuthentication; class User extends Authenticatable implements TwoFactorAuthenticatable { use HasTwoFactorAuthentication; // ... }
2. Token Issuer (Decoupled Auth)
The package never imposes a token library on your app. When a user completes a 2FA challenge, the package delegates token creation to your TwoFactorTokenIssuer implementation.
Register the contract in your AppServiceProvider.php (or AuthServiceProvider.php):
Using Laravel Sanctum:
use Omaacha\TwoFactor\Contracts\TwoFactorTokenIssuer; use Omaacha\TwoFactor\Contracts\TwoFactorAuthenticatable; use Illuminate\Http\Request; public function register(): void { $this->app->bind(TwoFactorTokenIssuer::class, function () { return new class implements TwoFactorTokenIssuer { public function issueToken(TwoFactorAuthenticatable $user, Request $request): array { $token = $user->createToken('auth_token')->plainTextToken; return [ 'payload' => [ 'token' => $token, 'user' => $user, ], ]; } }; }); }
Using Custom JWT (e.g. php-open-source-saver/jwt-auth or tymon/jwt-auth):
$this->app->bind(TwoFactorTokenIssuer::class, function () { return new class implements TwoFactorTokenIssuer { public function issueToken(TwoFactorAuthenticatable $user, Request $request): array { $token = auth('api')->login($user); return [ 'payload' => [ 'token' => $token, 'user' => $user, ], ]; } }; });
π‘οΈ Route Protection & Sudo Mode
Sudo Mode Middleware (2fa.sudo)
Require the user to have verified a 2FA code within the last 15 minutes before executing high-risk operations:
Route::delete('/api/v1/projects/{id}', [ProjectController::class, 'destroy']) ->middleware(['auth:api', '2fa.sudo']);
If the Sudo confirmation has expired, the request returns HTTP 423 (Locked):
{
"message": "Two-factor confirmation required for this sensitive action.",
"two_factor_sudo_required": true
}
To unlock Sudo mode, send the 6-digit authenticator code or recovery code to:
POST /api/v1/auth/2fa/sudo/confirm Content-Type: application/json { "code": "123456" }
Enforce 2FA Middleware (2fa.enforce)
Require users to have 2FA enabled to access entire areas:
Route::group(['prefix' => 'admin', 'middleware' => ['auth:api', '2fa.enforce']], function () { // Only accessible if 2FA is active });
π± Multi-Channel OTP Drivers
By default, verification OTP codes are sent via Email. You can switch or extend delivery channels seamlessly.
Switch Channel in .env:
TWO_FACTOR_OTP_CHANNEL=mail # Options: "mail", "notification", or custom driver
Extending with Custom Drivers (e.g., Twilio SMS):
In AppServiceProvider.php:
use Omaacha\TwoFactor\Channels\OtpChannelManager; use Omaacha\TwoFactor\Channels\Contracts\OtpChannelDriver; use Omaacha\TwoFactor\Contracts\TwoFactorAuthenticatable; public function boot(): void { app(OtpChannelManager::class)->extend('sms', function ($app) { return new class implements OtpChannelDriver { public function send(TwoFactorAuthenticatable $user, string $code, int $ttlMinutes): bool { // Example: Send SMS using Twilio Client $twilio = new \Twilio\Rest\Client(env('TWILIO_SID'), env('TWILIO_AUTH_TOKEN')); $twilio->messages->create($user->phone, [ 'from' => env('TWILIO_FROM'), 'body' => "Your security verification code is {$code}. Valid for {$ttlMinutes} minutes.", ]); return true; } }; }); }
π‘ API Endpoints Reference
All routes are prefixed under /api/v1/auth/2fa by default:
Challenge Resolution (Unauthenticated)
| Method | Endpoint | Description |
|---|---|---|
POST |
/verify |
Verify 6-digit TOTP code during sign-in |
POST |
/email/request |
Request an email OTP code |
POST |
/email/verify |
Verify email OTP code |
POST |
/recovery/verify |
Consume a single-use recovery code |
POST |
/invitation/skip |
Skip 2FA onboarding (activates 24h grace window) |
POST |
/invitation/confirm |
Confirm 2FA onboarding via TOTP |
User Management (Authenticated)
| Method | Endpoint | Description |
|---|---|---|
GET |
/status |
Get 2FA status, devices count, and recovery codes remaining |
POST |
/ownership/request |
Request an email OTP before enabling 2FA |
POST |
/ownership/verify |
Verify ownership OTP code |
POST |
/enable |
Generate secret key and SVG QR code |
POST |
/confirm |
Confirm setup with authenticator code & get recovery codes |
POST |
/disable |
Disable 2FA (requires password + code) |
POST |
/recovery-codes/regenerate |
Regenerate all 10 recovery codes |
GET |
/devices |
List active trusted devices |
DELETE |
/devices/{id} |
Revoke a single trusted device |
DELETE |
/devices |
Revoke all trusted devices |
POST |
/sudo/confirm |
Confirm 2FA code to unlock Sudo Mode |
βοΈ Configuration Reference
Inspect config/two-factor.php for all customizable settings:
return [ 'user_model' => env('TWO_FACTOR_USER_MODEL', App\Models\User::class), 'issuer' => env('TWO_FACTOR_ISSUER', config('app.name', 'Laravel')), 'otp_channel' => env('TWO_FACTOR_OTP_CHANNEL', 'mail'), 'challenge_ttl' => env('TWO_FACTOR_CHALLENGE_TTL', 15), // minutes 'email_otp_ttl' => env('TWO_FACTOR_EMAIL_OTP_TTL', 5), // minutes 'email_otp_cooldown' => env('TWO_FACTOR_EMAIL_OTP_COOLDOWN', 60), // seconds 'grace_period_hours' => env('TWO_FACTOR_GRACE_PERIOD_HOURS', 24), // hours 'sudo_mode_ttl' => env('TWO_FACTOR_SUDO_MODE_TTL', 15), // minutes 'trusted_devices' => [ 'enabled' => env('TWO_FACTOR_TRUSTED_DEVICES_ENABLED', true), 'ttl_days' => env('TWO_FACTOR_DEVICE_TTL_DAYS', 14), 'max_per_user'=> env('TWO_FACTOR_MAX_DEVICES_PER_USER', 3), 'cookie_name' => env('TWO_FACTOR_COOKIE_NAME', 'omaacha_device_trust'), ], 'routes' => [ 'prefix' => 'api/v1/auth/2fa', 'middleware' => ['api'], 'auth_middleware' => ['api'], ], ];
π Localization
Publish translation files:
php artisan vendor:publish --tag=two-factor-translations
Translations are available in:
- π¬π§ English (
en) - π«π· French (
fr) - πͺπΈ Spanish (
es) - πΈπ¦ Arabic (
ar)
π§ͺ Testing
composer test
π Security
If you discover a security vulnerability within omaacha/2fa, please send an email to maacha98otmane@gmail.com. All security vulnerabilities will be promptly addressed.
π€ Credits
- Otmane Maacha - Creator & Maintainer
- Built with standard RFC 6238 / RFC 4226 algorithms and Nayuki vector SVG.
π License
The MIT License (MIT). Please see License File for more information.
