hamzi / vaultic
Vaultic v4.0 - Laravel 13 WebAuthn/Passkeys authentication package.
Requires
- php: ^8.3
- illuminate/auth: ^13.0
- illuminate/cache: ^13.0
- illuminate/contracts: ^13.0
- illuminate/database: ^13.0
- illuminate/events: ^13.0
- illuminate/http: ^13.0
- illuminate/routing: ^13.0
- illuminate/session: ^13.0
- illuminate/support: ^13.0
- illuminate/validation: ^13.0
- illuminate/view: ^13.0
Requires (Dev)
- orchestra/testbench: ^11.0
- phpunit/phpunit: ^11.0
README
Vaultic is a Laravel package for WebAuthn/Passkeys (FIDO2) with challenge storage, fallback authentication flows, multi-guard support, and web/API-ready response handling.
This v4.X release is optimized for Laravel 13 projects and modern PHP 8.3+ runtimes.
Compatibility
- PHP
^8.3 - Laravel
13.x
Version Matrix
| Vaultic Tag | PHP | Laravel |
|---|---|---|
| v1.0.2 | 7.1.3+ | 5.5-5.8 |
| v1.2.1 | 7.2.5+ | 6.x |
| v1.3.0 | 7.2.5+ | 7.x |
| v3.0.1 | 8.0.2+ | 9.x |
| v3.1.0 | 8.1+ | 10.x |
| v3.2.1 | 8.2+ | 11.x |
| v3.3.1 | 8.2+ | 12.x |
| v4.0.0 | 8.3+ | 13.x |
Architecture
Vaultic uses a layered architecture to keep framework glue and business logic separated:
- HTTP Layer: controllers + middleware
- Service Layer: WebAuthn orchestration and fallback decisions
- Repository Layer: passkey persistence abstraction
- Contracts Layer: interfaces for verifier, token issuing, service, and repository
Flow:
- Controller -> Service -> Repository -> Eloquent model
Highlights
- Multi-guard authentication with per-guard model and identifier resolution
- Stateful web flows and stateless API flows from the same package endpoints
- UI-agnostic JSON endpoints that work with Blade, Livewire, Inertia, Vue, React, or native mobile clients
- Polymorphic passkey ownership, so passkeys can belong to different authenticatable models
- Optional token issuing abstraction for API guards, including a built-in Sanctum-friendly issuer
- Activity visibility in the management view, including last usage time and last IP used for a passkey
Installation
composer require hamzi/vaultic
Publish package assets:
php artisan vendor:publish --provider="Hamzi\\Vaultic\\VaulticServiceProvider" --tag=vaultic-config php artisan vendor:publish --provider="Hamzi\\Vaultic\\VaulticServiceProvider" --tag=vaultic-migrations php artisan vendor:publish --provider="Hamzi\\Vaultic\\VaulticServiceProvider" --tag=vaultic-views php artisan migrate
Laravel package discovery is enabled by default. Manual registration remains available when needed:
Configuration
Vaultic ships with config/vaultic.php.
Vaultic no longer requires package-specific .env entries for the default setup.
By default, Vaultic derives the relying party from your main Laravel application settings (APP_URL, APP_NAME) and uses the default cache store configured in your app:
// config/vaultic.php 'cache' => [ 'store' => config('cache.default', 'file'), // Uses your app's default cache store 'prefix' => 'vaultic:challenge:', 'ttl' => 300, ], 'auth' => [ 'default_guard' => 'web', 'guards' => [ 'web' => [ 'guard' => 'web', 'provider_model' => App\Models\User::class, 'identifier_column' => 'email', ], 'api' => [ 'guard' => 'sanctum', 'provider_model' => App\Models\User::class, 'identifier_column' => 'email', 'token_issuer' => Hamzi\Vaultic\Services\SanctumApiTokenIssuer::class, ], ], ], 'rate_limit' => [ 'attempts' => 10, 'decay_seconds' => 60, ], 'resident_key' => 'required', 'authenticator_hints' => ['client-device', 'hybrid'], 'security' => [ 'store_last_used_ip' => true, ], 'fallback' => [ 'driver' => 'password', ],
If your app runs behind Cloudflare, Nginx, or a load balancer, configure Laravel trusted proxies correctly so passkey activity IP addresses are recorded accurately.
Guard configuration lives under auth.guards in config/vaultic.php. Each guard can define:
provider_modelidentifier_columnstatefulremembertoken_issuer
For modern passkey UX, Vaultic enables discoverable credentials by default and can hint browsers toward:
client-device: Face ID, Touch ID, Windows Hello, built-in biometricshybrid: phone-assisted sign-in flows that typically rely on proximity transports such as Bluetooth
Browser and Device Coverage
Vaultic follows the WebAuthn/FIDO2 standard and supports platform passkeys (Face ID, Touch ID, Windows Hello), hybrid phone flows, and roaming authenticators (USB/NFC/BLE security keys) when the client browser/device supports them.
In production, always test your own target matrix (OS + browser + authenticator type), because browser vendors may differ in UX details, transport handling, and conditional behavior.
Routes
Vaultic exposes two channels by default:
-
Web routes under
/passkeyswith name prefixvaultic. -
API routes under
/api/passkeyswith name prefixvaultic.api. -
POST /passkeys/register/options->vaultic.register.options -
POST /passkeys/register->vaultic.register.store -
POST /passkeys/authenticate/options->vaultic.authenticate.options -
POST /passkeys/authenticate->vaultic.authenticate.store -
DELETE /passkeys/{passkey}->vaultic.passkeys.destroy -
POST /api/passkeys/register/options->vaultic.api.register.options -
POST /api/passkeys/register->vaultic.api.register.store -
POST /api/passkeys/authenticate/options->vaultic.api.authenticate.options -
POST /api/passkeys/authenticate->vaultic.api.authenticate.store
Rate limiting is applied using your Vaultic config values (rate_limit.attempts, rate_limit.decay_seconds) through Laravel throttle middleware.
All endpoints return JSON, so the package is not tied to any UI stack.
WebAuthn Verifier Contract
Vaultic does not force a specific FIDO2 vendor package. Bind your own verifier implementation:
$this->app->bind( Hamzi\Vaultic\Contracts\WebAuthnVerifier::class, App\Security\MyWebAuthnVerifier::class );
If no verifier is bound, Vaultic throws a clear runtime exception instead of silently failing.
API Token Issuing
For stateless guards, Vaultic can return token payloads after a successful passkey assertion.
Bind your own issuer:
$this->app->bind( Hamzi\Vaultic\Contracts\ApiTokenIssuer::class, App\Auth\IssueVaulticToken::class );
Or use the included Sanctum-oriented issuer when your authenticatable model exposes createToken():
'auth' => [ 'guards' => [ 'api' => [ 'guard' => 'sanctum', 'provider_model' => App\Models\User::class, 'identifier_column' => 'email', 'stateful' => false, 'token_issuer' => Hamzi\Vaultic\Services\SanctumApiTokenIssuer::class, ], ], ],
Successful API authentication responses include a tokens array in the JSON payload.
Blade Integration
Vaultic now ships with publishable Tailwind-ready Blade primitives on top of the JSON endpoints, so you can use the package without building your own JavaScript WebAuthn bridge first.
Passkey Login Button
The default button is compact, Tailwind-based, and intentionally small so you can place it inline in forms, toolbars, modals, or profile screens.
Minimal discoverable login button:
<x-vaultic::passkey-button size="sm" />
Full-width button inside a card or auth form:
<x-vaultic::passkey-button size="md" :full-width="true" class="justify-center" />
Component usage:
<x-vaultic::passkey-button size="sm" label="Continue with passkey" />
Optional account-scoped login when you still want an identifier field:
<input id="email" type="email" name="email" autocomplete="username webauthn"> <x-vaultic::passkey-button identifier-selector="#email" size="sm" label="Sign in for this account" />
Directive usage:
@passkeyButton(['size' => 'sm', 'fullWidth' => false])
Helper usage:
{{ vaultic_passkey_button(['size' => 'sm']) }}
Passkey Management Panel
Render the Tailwind registration form and linked-passkeys table anywhere inside your authenticated Blade views:
<x-vaultic::passkey-panel />
The management panel stays fully Tailwind-based and includes:
- compact registration CTA
- discoverable passkey copy
- linked authenticators table
- last used timestamp and last used IP activity visibility
- device deletion actions
Or through the directive/helper APIs:
@passkeyPanel() {{ vaultic_passkey_panel() }}
Both primitives are customizable through props, route overrides, labels, sizes, width control, and by publishing the package views with vaultic-views.
Supported authenticator experiences depend on the browser and device, but Vaultic is configured to work well with:
- Face ID
- Touch ID
- Windows Hello
- Nearby phones in hybrid flows
- USB, NFC, and BLE security keys
Frontend Integration
If you prefer a custom client, Vaultic still exposes the raw JSON endpoints for:
- Blade pages with your own markup
- Inertia or SPA frontends
- Native mobile or hybrid clients through API routes
- Admin panels with separate guards and authenticatable models
Middleware
Use passkey.required for routes that must have a passkey-authenticated session.
Route::middleware(['auth', 'passkey.required'])->group(function () { Route::get('/settings/security', function () { return 'ok'; }); });
Events
Hamzi\Vaultic\Events\PasskeyRegisteredHamzi\Vaultic\Events\PasskeyAuthenticatedHamzi\Vaultic\Events\AuthenticationFailed
Tests
composer test
Includes:
- unit tests for challenge issuance/pull behavior
- feature tests for route registration and middleware behavior
- service tests for multi-guard resolution, stateful login flow, and stateless token payloads
Repository Standards
- License: LICENSE
- Contribution Guide: CONTRIBUTING.md
- Security Policy: SECURITY.md
- Code of Conduct: CODE_OF_CONDUCT.md
- Changelog: CHANGELOG.md
License
vaultic is open-sourced software licensed under the MIT license. See LICENSE for more details.