Trusted-device management for Laravel applications.
Laravel Trust Vault gives Laravel applications a clean, server-controlled "trust this device" flow. It uses high-entropy rotating credentials, stores only a hash of the secret validator, supports revocation and expiration, and includes Laravel-native actions, middleware, resources, requests, events, notifications, auditing, commands, and tests.
A "trusted device" should not mean "this browser has the same User-Agent and IP address." Those values are useful metadata, but they are not proof of identity.
Trust Vault instead issues a random selector + validator credential after your application completes a step-up authentication flow such as:
- password confirmation
- TOTP / MFA
- passkey verification
- recovery verification
The browser receives the credential in a secure cookie. The database stores the selector and a SHA-256 hash of the validator.
- High-entropy selector + validator credentials
- Server-side validator hashing
- Automatic credential rotation
- Sliding expiration with a hard absolute expiration
- Current-device recognition
- Trust, rename, revoke, revoke-current, revoke-others, and revoke-all flows
- Maximum trusted-device limits
- Lightweight browser / platform metadata
- Throttled
last_seen_atwrites - Polymorphic owners
trusted-devicemiddleware- JSON resources and Form Requests
- Invokable controllers
- Activity history
- Security events
- Optional email notifications
- Install and prune Artisan commands
- Pint with the Laravel preset
- Larastan / PHPStan
- PHPUnit / Orchestra Testbench
- Laravel 12 and Laravel 13 CI matrix
composer require eloquentworks/laravel-trust-vault
php artisan trust-vault:install
php artisan migrateAdd the trait to your authenticatable model:
use EloquentWorks\TrustVault\Concerns\HasTrustedDevices;
class User extends Authenticatable
{
use HasTrustedDevices;
}Only call trust() after successful step-up authentication:
use EloquentWorks\TrustVault\Facades\TrustVault;
$device = TrustVault::trust(
owner: auth()->user(),
request: request(),
name: 'Gaming PC',
);if (TrustVault::check(auth()->user(), request())) {
// The browser possesses a valid trusted-device credential.
}Get the full device:
$device = TrustVault::current(
auth()->user(),
request(),
);Trust Vault registers the trusted-device middleware alias:
Route::middleware([
'auth',
'trusted-device',
])->group(function () {
Route::get(
'/security/dashboard',
SecurityDashboardController::class,
);
});For high-risk actions, trusted-device status should complement fresh authentication and authorization rather than replace them.
TrustVault::rename(
device: $device,
name: 'Home PC',
request: request(),
);
TrustVault::revoke(
device: $device,
reason: 'user_removed',
request: request(),
);
TrustVault::revokeCurrent(
owner: auth()->user(),
request: request(),
);
TrustVault::revokeOthers(
owner: auth()->user(),
request: request(),
);
TrustVault::revokeAll(
owner: auth()->user(),
);$user->trustedDevices()->get();
$user->activeTrustedDevices()->get();
$user->revokeTrustedDevices();The management routes are enabled by default:
GET /trust-vault/devices
PATCH /trust-vault/devices/{uuid}
DELETE /trust-vault/devices/current
DELETE /trust-vault/devices/others
DELETE /trust-vault/devices/all
DELETE /trust-vault/devices/{uuid}
Direct enrollment is disabled by default.
If you enable it:
'routes' => [
'enrollment' => [
'enabled' => true,
'middleware' => [
'password.confirm',
'throttle:6,1',
],
],
],the following route is registered:
POST /trust-vault/devices/current
Keep step-up middleware on this route.
Trust Vault dispatches:
TrustedDeviceCreated
TrustedDeviceRevoked
TrustedDeviceTokenRotated
TrustedDeviceCompromised
UntrustedDeviceDetected
A validator mismatch revokes the matching selector record and emits
TrustedDeviceCompromised.
Activity recording is enabled by default for meaningful security changes:
trusted
renamed
token_rotated
revoked
compromised
Normal page views are intentionally not written to the activity table.
'notifications' => [
'trusted' => false,
'revoked' => false,
'compromised' => true,
],The authenticatable model must support Laravel notifications.
Defaults:
'lifetime_days' => 30,
'absolute_lifetime_days' => 90,
'rotate_after_days' => 7,Rotation can renew the normal expiry, but never beyond
absolute_expires_at.
'max_devices_per_owner' => 10,When the limit is reached, the least recently seen active device is revoked before the new device is created.
Trust Vault does not write last_seen_at on every request.
'metadata' => [
'touch_interval_seconds' => 300,
],This keeps the trusted-device middleware practical on frequently hit routes.
php artisan trust-vault:pruneOr:
php artisan trust-vault:prune --days=60You may schedule the command from the host application.
composer format
composer analyse
composer test
composer checkpint.json uses Laravel's laravel preset.
- Use HTTPS in production.
- Keep the cookie
secureandhttp_only. - Keep Laravel's cookie encryption middleware enabled.
- Enroll a trusted device only after step-up authentication.
- Revoke trusted devices after account recovery where appropriate.
- Consider revoking all trusted devices after a password reset.
- Do not treat IP addresses or browser fingerprints as identity proof.
- Do not use trusted-device status as an authorization role.
- Require fresh authentication for especially sensitive actions.
- PHP 8.2+ for Laravel 12
- PHP 8.3+ for Laravel 13
- Laravel 12.x / 13.x
MIT