Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Spatie Laravel Passkeys
https://github.com/spatie/laravel-passkeys
Admin
A Laravel package that provides a simple way to integrate passkey authentication into your
...
Tokens:
9,104
Snippets:
63
Trust Score:
8.5
Update:
5 months ago
Context
Skills
Chat
Benchmark
79.1
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Laravel Passkeys Laravel Passkeys is a comprehensive package by Spatie that enables passwordless authentication in Laravel applications using WebAuthn passkeys. This package eliminates the need for traditional passwords by allowing users to authenticate using biometric data, security keys, or device-based credentials stored in password managers like 1Password, macOS Keychain, or browser password managers. The package provides a seamless integration with Laravel through Livewire components for passkey registration and Blade components for authentication. It leverages the web-auth/webauthn-lib library to handle the WebAuthn protocol, offering a production-ready solution for implementing modern, secure authentication flows. The package includes customizable actions for all core operations, comprehensive localization support for 14 languages, and full event-driven architecture for tracking authentication events. ## Installation and Configuration Setup the package and configure relying party settings. ```bash # Install via Composer composer require spatie/laravel-passkeys # Publish configuration file php artisan vendor:publish --tag="passkeys-config" # Publish and run migrations php artisan vendor:publish --tag="passkeys-migrations" php artisan migrate # Register routes in routes/web.php Route::passkeys(); # Or with custom prefix Route::passkeys('auth/passkeys'); ``` ```php // config/passkeys.php - Configure relying party and models return [ 'redirect_to_after_login' => '/dashboard', 'actions' => [ 'generate_passkey_register_options' => Spatie\LaravelPasskeys\Actions\GeneratePasskeyRegisterOptionsAction::class, 'store_passkey' => Spatie\LaravelPasskeys\Actions\StorePasskeyAction::class, 'generate_passkey_authentication_options' => Spatie\LaravelPasskeys\Actions\GeneratePasskeyAuthenticationOptionsAction::class, 'find_passkey' => Spatie\LaravelPasskeys\Actions\FindPasskeyToAuthenticateAction::class, ], 'relying_party' => [ 'name' => config('app.name'), 'id' => parse_url(config('app.url'), PHP_URL_HOST), 'icon' => null, ], 'models' => [ 'passkey' => Spatie\LaravelPasskeys\Models\Passkey::class, 'authenticatable' => App\Models\User::class, ], ]; ``` ## User Model Setup Add passkey functionality to authenticatable models. ```php // app/Models/User.php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Spatie\LaravelPasskeys\Models\Concerns\InteractsWithPasskeys; use Spatie\LaravelPasskeys\Models\Concerns\HasPasskeys; class User extends Authenticatable implements HasPasskeys { use InteractsWithPasskeys; protected $fillable = ['name', 'email', 'password']; // The InteractsWithPasskeys trait provides default implementations: // - passkeys(): HasMany relationship // - getPasskeyName(): returns $this->email // - getPasskeyId(): returns $this->id // - getPasskeyDisplayName(): returns $this->name // Optional: Override to customize passkey user entity public function getPasskeyName(): string { return $this->email; } public function getPasskeyDisplayName(): string { return $this->name ?? $this->email; } public function getPasskeyId(): string { return (string) $this->id; } } ``` ## Passkey Registration Component Display and manage user passkeys with Livewire. ```blade {{-- resources/views/profile/passkeys.blade.php --}} <x-app-layout> <div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8"> <div class="bg-white shadow rounded-lg p-6"> <h2 class="text-2xl font-bold mb-6">Manage Passkeys</h2> {{-- Livewire component for passkey management --}} <livewire:passkeys /> </div> </div> </x-app-layout> ``` ```php // The PasskeysComponent handles: // 1. Displaying existing passkeys with last used timestamps // 2. Creating new passkeys with custom names // 3. Deleting passkeys // 4. Validating and storing passkey credentials // app/Livewire/CustomPasskeysComponent.php - Optional customization namespace App\Livewire; use Spatie\LaravelPasskeys\Livewire\PasskeysComponent; class CustomPasskeysComponent extends PasskeysComponent { // Override to add custom validation rules protected function rules(): array { return [ 'name' => 'required|string|max:255|unique:passkeys,name,' . auth()->id() . ',authenticatable_id', ]; } } ``` ## Passkey Authentication Component Add passkey login to authentication forms. ```blade {{-- resources/views/auth/login.blade.php --}} <x-guest-layout> <div class="mb-4"> <h2 class="text-2xl font-bold">Login</h2> </div> {{-- Traditional email/password form --}} <form method="POST" action="{{ route('login') }}"> @csrf <div class="mb-4"> <label for="email">Email</label> <input type="email" name="email" id="email" required> </div> <div class="mb-4"> <label for="password">Password</label> <input type="password" name="password" id="password" required> </div> <button type="submit">Login</button> </form> <div class="mt-6 text-center"> <div class="relative"> <div class="absolute inset-0 flex items-center"> <div class="w-full border-t border-gray-300"></div> </div> <div class="relative flex justify-center text-sm"> <span class="px-2 bg-white text-gray-500">Or</span> </div> </div> {{-- Passkey authentication component with custom redirect --}} <div class="mt-4"> <x-authenticate-passkey redirect="/dashboard"> <button class="w-full flex items-center justify-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"> <svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20"> <path d="M10 2a5 5 0 00-5 5v2a2 2 0 00-2 2v5a2 2 0 002 2h10a2 2 0 002-2v-5a2 2 0 00-2-2H7V7a3 3 0 015.905-.75 1 1 0 001.937-.5A5.002 5.002 0 0010 2z"/> </svg> Sign in with Passkey </button> </x-authenticate-passkey> </div> </div> </x-guest-layout> ``` ## Generate Passkey Registration Options Create WebAuthn options for passkey registration. ```php // app/Actions/CustomGeneratePasskeyRegisterOptionsAction.php namespace App\Actions; use Spatie\LaravelPasskeys\Actions\GeneratePasskeyRegisterOptionsAction; use Spatie\LaravelPasskeys\Models\Concerns\HasPasskeys; use Webauthn\AuthenticatorSelectionCriteria; use Webauthn\PublicKeyCredentialCreationOptions; class CustomGeneratePasskeyRegisterOptionsAction extends GeneratePasskeyRegisterOptionsAction { public function execute(HasPasskeys $authenticatable, bool $asJson = true): string|PublicKeyCredentialCreationOptions { // Customize authenticator selection criteria $options = parent::execute($authenticatable, false); return $asJson ? Serializer::make()->toJson($options) : $options; } // Override to require user verification public function authenticatorSelection(): AuthenticatorSelectionCriteria { return new AuthenticatorSelectionCriteria( authenticatorAttachment: null, // null allows both platform and cross-platform userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED, residentKey: AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_REQUIRED, ); } } ``` ```php // Register custom action in config/passkeys.php 'actions' => [ 'generate_passkey_register_options' => App\Actions\CustomGeneratePasskeyRegisterOptionsAction::class, // ... other actions ], ``` ## Store Passkey Action Validate and persist passkey credentials. ```php // app/Actions/CustomStorePasskeyAction.php namespace App\Actions; use Spatie\LaravelPasskeys\Actions\StorePasskeyAction; use Spatie\LaravelPasskeys\Models\Concerns\HasPasskeys; use Spatie\LaravelPasskeys\Models\Passkey; use Illuminate\Support\Facades\Log; class CustomStorePasskeyAction extends StorePasskeyAction { public function execute( HasPasskeys $authenticatable, string $passkeyJson, string $passkeyOptionsJson, string $hostName, array $additionalProperties = [], ): Passkey { // Add custom metadata $additionalProperties['user_agent'] = request()->userAgent(); $additionalProperties['ip_address'] = request()->ip(); $additionalProperties['created_from'] = 'web'; $passkey = parent::execute( $authenticatable, $passkeyJson, $passkeyOptionsJson, $hostName, $additionalProperties ); // Log passkey creation Log::info('Passkey created', [ 'user_id' => $authenticatable->getKey(), 'passkey_id' => $passkey->id, 'name' => $additionalProperties['name'] ?? 'Unnamed', ]); // Send notification $authenticatable->notify(new PasskeyCreatedNotification($passkey)); return $passkey; } } ``` ## Authenticate Using Passkey Handle passkey-based authentication requests. ```php // The package automatically registers these routes via Route::passkeys(): // GET /passkeys/authentication-options - Generate authentication challenge // POST /passkeys/authenticate - Verify passkey and log in user // Example JavaScript integration (typically in your Blade view) ``` ```javascript // resources/js/passkey-auth.js async function authenticateWithPasskey() { try { // 1. Get authentication options from server const optionsResponse = await fetch('/passkeys/authentication-options', { method: 'GET', headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'include' }); const options = await optionsResponse.json(); // 2. Convert base64 strings to ArrayBuffers options.challenge = base64ToArrayBuffer(options.challenge); // 3. Request credential from authenticator const credential = await navigator.credentials.get({ publicKey: options }); // 4. Prepare credential data for server const credentialData = { id: credential.id, rawId: arrayBufferToBase64(credential.rawId), type: credential.type, response: { authenticatorData: arrayBufferToBase64(credential.response.authenticatorData), clientDataJSON: arrayBufferToBase64(credential.response.clientDataJSON), signature: arrayBufferToBase64(credential.response.signature), userHandle: credential.response.userHandle ? arrayBufferToBase64(credential.response.userHandle) : null } }; // 5. Send to server for verification const form = document.getElementById('passkey-login-form'); const input = document.createElement('input'); input.type = 'hidden'; input.name = 'start_authentication_response'; input.value = JSON.stringify(credentialData); form.appendChild(input); form.submit(); } catch (error) { console.error('Passkey authentication failed:', error); alert('Authentication failed. Please try again or use password.'); } } // Helper functions for base64 encoding/decoding function base64ToArrayBuffer(base64) { const binaryString = window.atob(base64); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; } function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary); } ``` ## Passkey Model and Relationships Access and manage stored passkey credentials. ```php // Working with the Passkey model use Spatie\LaravelPasskeys\Models\Passkey; use App\Models\User; // Get all passkeys for a user $user = User::find(1); $passkeys = $user->passkeys; // Get recently used passkeys $recentPasskeys = $user->passkeys() ->whereNotNull('last_used_at') ->orderByDesc('last_used_at') ->take(5) ->get(); // Find specific passkey $passkey = Passkey::find(1); $owner = $passkey->authenticatable; // Returns the User model // Access passkey data $credentialId = $passkey->credential_id; $publicKeyCredentialSource = $passkey->data; // Returns PublicKeyCredentialSource object $lastUsed = $passkey->last_used_at; // Carbon instance $name = $passkey->name; // User-defined name like "MacBook Pro" or "iPhone" // Delete passkey $passkey->delete(); // Update last used timestamp $passkey->update(['last_used_at' => now()]); ``` ```php // database/migrations/xxxx_create_passkeys_table.php Schema::create('passkeys', function (Blueprint $table) { $table->id(); $table->foreignIdFor(User::class, 'authenticatable_id') ->constrained('users') ->cascadeOnDelete(); $table->text('name'); // User-friendly name $table->text('credential_id'); // Base64 encoded credential ID $table->json('data'); // Serialized PublicKeyCredentialSource $table->timestamp('last_used_at')->nullable(); $table->timestamps(); }); ``` ## Listen to Passkey Authentication Events Track when passkeys are used for authentication. ```php // app/Listeners/LogPasskeyAuthentication.php namespace App\Listeners; use Spatie\LaravelPasskeys\Events\PasskeyUsedToAuthenticateEvent; use Illuminate\Support\Facades\Log; use App\Models\LoginHistory; class LogPasskeyAuthentication { public function handle(PasskeyUsedToAuthenticateEvent $event): void { $passkey = $event->passkey; $request = $event->request; $user = $passkey->authenticatable; // Update last used timestamp $passkey->update(['last_used_at' => now()]); // Log authentication event Log::info('User authenticated with passkey', [ 'user_id' => $user->id, 'passkey_id' => $passkey->id, 'passkey_name' => $passkey->name, 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), ]); // Store in database for audit trail LoginHistory::create([ 'user_id' => $user->id, 'method' => 'passkey', 'passkey_id' => $passkey->id, 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), 'successful' => true, 'logged_in_at' => now(), ]); // Send notification for security monitoring if ($this->isUnusualLogin($user, $request)) { $user->notify(new UnusualLoginDetectedNotification($passkey, $request)); } } private function isUnusualLogin($user, $request): bool { // Check if login from new location or device $recentLogins = LoginHistory::where('user_id', $user->id) ->where('created_at', '>', now()->subDays(30)) ->get(); return !$recentLogins->contains('ip_address', $request->ip()); } } ``` ```php // app/Providers/EventServiceProvider.php namespace App\Providers; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Spatie\LaravelPasskeys\Events\PasskeyUsedToAuthenticateEvent; use App\Listeners\LogPasskeyAuthentication; class EventServiceProvider extends ServiceProvider { protected $listen = [ PasskeyUsedToAuthenticateEvent::class => [ LogPasskeyAuthentication::class, ], ]; } ``` ## Custom Passkey Configuration and Actions Extend and customize core package behavior. ```php // app/Actions/FindPasskeyToAuthenticateAction.php namespace App\Actions; use Spatie\LaravelPasskeys\Actions\FindPasskeyToAuthenticateAction as BaseAction; use Spatie\LaravelPasskeys\Models\Passkey; class CustomFindPasskeyToAuthenticateAction extends BaseAction { public function execute(string $credentialJson, string $optionsJson): ?Passkey { $passkey = parent::execute($credentialJson, $optionsJson); if (!$passkey) { return null; } // Add custom validation if ($passkey->authenticatable->is_suspended) { \Log::warning('Suspended user attempted passkey login', [ 'user_id' => $passkey->authenticatable_id ]); return null; } // Check if passkey is expired (custom business logic) if ($passkey->created_at->addMonths(6)->isPast()) { \Log::warning('Expired passkey used', ['passkey_id' => $passkey->id]); $passkey->delete(); return null; } return $passkey; } } ``` ```php // config/passkeys.php - Register all custom actions return [ 'redirect_to_after_login' => '/dashboard', 'actions' => [ 'generate_passkey_register_options' => App\Actions\CustomGeneratePasskeyRegisterOptionsAction::class, 'store_passkey' => App\Actions\CustomStorePasskeyAction::class, 'generate_passkey_authentication_options' => Spatie\LaravelPasskeys\Actions\GeneratePasskeyAuthenticationOptionsAction::class, 'find_passkey' => App\Actions\CustomFindPasskeyToAuthenticateAction::class, 'configure_ceremony_step_manager_factory' => Spatie\LaravelPasskeys\Actions\ConfigureCeremonyStepManagerFactoryAction::class, ], 'relying_party' => [ 'name' => config('app.name'), 'id' => env('PASSKEY_RELYING_PARTY_ID', parse_url(config('app.url'), PHP_URL_HOST)), 'icon' => env('PASSKEY_RELYING_PARTY_ICON'), ], 'models' => [ 'passkey' => Spatie\LaravelPasskeys\Models\Passkey::class, 'authenticatable' => App\Models\User::class, ], ]; ``` ## Summary Laravel Passkeys provides a complete, production-ready solution for implementing passwordless authentication in Laravel applications. The package handles the complex WebAuthn protocol through an intuitive API, allowing users to register and authenticate using biometric sensors, security keys, or platform authenticators. This modern authentication method improves both security and user experience by eliminating password-related vulnerabilities like phishing, credential stuffing, and password reuse. The package is designed with flexibility and extensibility in mind. All core operations are implemented as customizable action classes, allowing developers to extend functionality for specific business requirements such as rate limiting, audit logging, or multi-factor authentication flows. The package integrates seamlessly with Laravel's authentication system, supports multiple authenticatable models, provides comprehensive event hooks for tracking usage, and includes localization support for international applications. Whether you're building a new application or adding passkey support to an existing one, Laravel Passkeys provides the foundation for secure, modern authentication.