skywalker-labs / passwordless
Seamless Passwordless Authentication for Laravel. Integrate OTP (One-Time Password) Login and 2FA into your default authentication flow with zero-conf middleware and ready-to-use UI.
Fund package maintenance!
Requires
- php: ^8.2
- skywalker-labs/toolkit: ^1.6.0
Requires (Dev)
- infection/infection: ^0.32
- larastan/larastan: ^3.9
- laravel/pint: ^1.13
- orchestra/testbench: ^9.0
- phpstan/phpstan: ^2.0
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^10.5
README
Elegant passwordless authentication for Laravel. Drop-in OTP login, 2FA enforcement, magic links, and backup codes โ all built on Skywalker Toolkit with action-oriented architecture, contract-based design, and Extreme Strictness (PHPStan Level 9 + Strict Rules) compliance.
โจ Features
| Feature | Detail |
|---|---|
| OTP Login | Generate & verify time-limited one-time passwords |
| Hashed Storage | OTPs and backup codes stored with Hash::make() โ never plain-text |
| Magic Login Links | Signed, temporary URLs for one-click authentication |
| Backup Codes | Hashed emergency recovery codes |
| Multi-Channel | Email, SMS (Twilio), Slack, and Log channels |
| Event-Driven | OtpGenerated, OtpVerified, OtpFailed events for full extensibility |
| Middleware Gate | otp.verified middleware with infinite-loop protection |
| Rate Limiting | Built-in per-identifier request throttling on all routes |
| Action Architecture | Each operation is a dedicated Action class (SRP) |
| Extreme Strictness | 100% compliance with PHPStan Level 9 + Strict & Deprecation rules |
| Zero-Trust Auth | Risk-based trust scoring (TrustEngine) integrated into the core flow |
| Strict Types | declare(strict_types=1) and explicit type comparisons everywhere |
๐ฆ Installation
composer require skywalker-labs/passwordless
Requires: PHP โฅ 8.2, Laravel โฅ 11.0
๐ ๏ธ Setup
1. Add the HasOtp Trait to Your User Model
use Skywalker\Otp\Concerns\HasOtp; class User extends Authenticatable { use HasOtp; }
The trait provides sendOtp(): string and verifyOtp(string $token): bool methods.
2. Publish Config & Migrations
php artisan vendor:publish --tag=passwordless-config php artisan vendor:publish --tag=passwordless-migrations php artisan migrate
3. Configure (config/passwordless.php)
return [ 'length' => 6, // OTP digit length 'expiry' => 10, // Minutes until OTP expires 'driver' => 'cache', // 'cache' or 'database' 'channel' => 'mail', // 'mail' | 'log' | 'sms' | 'slack' 'middleware' => ['web', 'throttle:6,1'], 'services' => [ 'twilio' => [ 'sid' => env('TWILIO_SID'), 'token' => env('TWILIO_AUTH_TOKEN'), 'from' => env('TWILIO_FROM'), ], 'slack' => [ 'webhook_url' => env('SLACK_WEBHOOK_URL'), ], ], ];
๐ฏ Usage
Using the Facade
use Skywalker\Otp\Facades\Otp; // Send an OTP to an email or phone $otp = Otp::generate('user@example.com'); // Verify the submitted OTP try { Otp::verify('user@example.com', $request->otp); } catch (\Skywalker\Otp\Exceptions\InvalidOtpException $e) { // Invalid or expired OTP }
Dependency Injection (Recommended)
Inject the contract for testable, SOLID-compliant code:
use Skywalker\Otp\Domain\Contracts\OtpService; public function __construct(private readonly OtpService $otp) {} public function send(string $identifier): void { $this->otp->generate($identifier); }
Magic Login Links
// Generate a signed link valid for 15 minutes (configurable via 'expiry') $link = Otp::generateMagicLink('user@example.com'); // โ https://your-app.com/magic-login?identifier=...&signature=... // Route: GET /magic-login โ passwordless.magic-login // Validated automatically by hasValidSignature() in the controller
Backup Codes
// Generate 8 hashed recovery codes (stored securely in DB) $codes = Otp::generateBackupCodes('user@example.com'); // Returns: ['AbcD123fGh', 'xYz987Qrst', ...] โ shown once, stored hashed // Verify & consume a backup code (uses Hash::check internally) $ok = Otp::verifyBackupCode('user@example.com', $submittedCode);
Custom OTP Generator
use Skywalker\Otp\Facades\Otp; // Use a custom generator at runtime (e.g. alphanumeric, UUID-style) Otp::useGenerator(fn() => strtoupper(substr(md5(microtime()), 0, 6)));
Listen to Events
// In your EventServiceProvider or a Listener use Skywalker\Otp\Events\OtpVerified; use Skywalker\Otp\Events\OtpGenerated; use Skywalker\Otp\Events\OtpFailed; protected $listen = [ OtpVerified::class => [LogSuccessfulLogin::class], OtpGenerated::class => [NotifySecurityTeam::class], OtpFailed::class => [AlertOnRepeatedFailures::class], ];
Middleware Gate
Add the otp.verified middleware to any route to enforce OTP verification before access:
Route::middleware(['auth', 'otp.verified'])->group(function () { Route::get('/dashboard', DashboardController::class); });
The middleware automatically:
- Skips users without the
HasOtptrait - Allows access once
otp_verifiedis set in the session - Excludes OTP verify routes to prevent redirect loops
๐๏ธ Architecture
The package follows a strict Action-Oriented + Domain-Driven architecture:
src/
โโโ Actions/ โ One class per operation (SRP)
โ โโโ GenerateOtp โ Hash::make() + event dispatch
โ โโโ VerifyOtp โ Hash::check() + timing-safe
โ โโโ GenerateBackupCodes โ Hash::make() per code
โ โโโ VerifyBackupCode โ Hash::check() against stored hashes
โ โโโ GenerateMagicLink โ Signed URL generation
โโโ Concerns/
โ โโโ HasOtp โ User model trait (sendOtp / verifyOtp)
โโโ Domain/
โ โโโ Contracts/ โ OtpStore, OtpSender, OtpService interfaces
โ โโโ ValueObjects/
โ โโโ OtpToken โ Immutable (readonly) value object
โโโ Events/
โ โโโ OtpGenerated, OtpFailed, OtpVerified
โโโ Exceptions/
โ โโโ OtpException โ Extends PackageException (toolkit)
โ โโโ InvalidOtpException
โ โโโ OtpDeliveryFailedException
โโโ Facades/
โ โโโ Otp โ Static access via Laravel facade
โโโ Http/
โ โโโ Controllers/OtpAuthController โ Injects OtpService contract
โ โโโ Middleware/EnsureOtpVerified
โโโ Infrastructure/
โ โโโ Delivery/NotificationSender โ Multi-channel sender
โ โโโ Persistence/
โ โโโ CacheOtpStore โ driver=cache
โ โโโ DatabaseOtpStore โ driver=database
โโโ Services/
โ โโโ OtpService โ Orchestrator, delegates to Actions
โโโ OtpServiceProvider.php โ Bindings, routes, events, middleware
Toolkit foundation:
| Our Class | Extends |
|---|---|
| All 5 Action classes | Skywalker\Support\Foundation\Action |
OtpToken |
Skywalker\Support\Foundation\ValueObject |
OtpException |
Skywalker\Support\Exceptions\PackageException |
OtpService |
Skywalker\Support\Foundation\Service |
OtpServiceProvider |
Skywalker\Support\Providers\PackageServiceProvider |
๐งช Testing & Analysis
# Run tests composer test # Static analysis (PHPStan Level 9) composer analyse # Mutation testing (Infection) vendor/bin/infection # Format code (Laravel Pint / PSR-12) composer format
๐ Security & Quality
- Extreme Strictness โ 100% compliance with PHPStan Level 9 + Strict & Deprecation rules
- Zero-Trust Security โ Integrated
TrustEnginefor risk-based analysis - Hashed OTPs โ stored with
Hash::make(), verified withHash::check() - Hashed Backup Codes โ same approach as OTPs
- Signed Magic Links โ protection against link tampering
- Rate Limiting โ strict per-identifier request throttling
- Strict Logic โ no implicit type coercion, only explicit boolean comparisons
๐ Zero-Trust Security
This package integrates the Skywalker Trust Engine to provide risk-based authentication:
- Trust Scoring: Every authentication attempt calculates a "Trust Score" (0.0 to 1.0) based on IP, behavior, and environment.
- Adaptive 2FA: If a user has a very high trust score (e.g. > 0.8), the
otp.verifiedmiddleware can be configured to bypass the OTP check (bypass_high_trust). - Hijack Protection: If a session's trust score drops significantly mid-session, the user is automatically prompted for re-verification.
- Session Rotation: Sessions are regenerated upon successful OTP or Magic Link verification to prevent fixation attacks.
๐ฃ๏ธ Available Routes
| Method | URI | Name | Auth |
|---|---|---|---|
POST |
/otp/send |
otp.send |
Public |
POST |
/otp/verify |
otp.verify |
Public |
GET |
/otp/verify |
otp.verify.view |
auth |
POST |
/otp/verify-submit |
otp.verify.submit |
auth |
POST |
/otp/resend |
otp.resend |
auth |
GET |
/magic-login |
passwordless.magic-login |
Signed URL |
๐ License
The MIT License (MIT). Please see License File.