spatie/laravel-passkeys
Add passkey (WebAuthn) login to Laravel. Includes a Livewire component to create/register passkeys and a Blade component to authenticate users without passwords, using built-in OS/password manager passkey support.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require spatie/laravel-passkeys
php artisan vendor:publish --provider="Spatie\Passkeys\PasskeysServiceProvider"
php artisan migrate
config/passkeys.php) and creates the passkeys table.First Use Case:
Spatie\Passkeys\Facades\Passkey facade or service to generate a new passkey for a user and listen for events:
use Spatie\Passkeys\Facades\Passkey;
$user = User::find(1);
event(new \Spatie\Passkeys\Events\PasskeyRegisteredEvent($passkey = Passkey::createForUser($user)));
$user = User::find(1);
$isValid = Passkey::verify($user, $request->input('passkey'));
Frontend Integration:
@passkeyCreate and @passkeyVerify) or JavaScript SDK (e.g., WebAuthn API) to interact with the browser’s passkey system.@passkeyCreate('user', 'example.com', 'My App')
Flow:
$user = User::create([...]);
$passkey = Passkey::createForUser($user, $request->input('credential'));
event(new \Spatie\Passkeys\Events\PasskeyRegisteredEvent($passkey));
PasskeyRegisteredEvent::listen(function ($event) {
Log::info("New passkey registered for user {$event->passkey->user_id}");
// Send welcome email, update analytics, etc.
});
passkeys table.Customization:
Spatie\Passkeys\Models\Passkey model to add custom fields (e.g., device_type, is_primary).createForUser method in a service class for additional logic.Flow:
$user = User::find($request->user_id);
if (Passkey::verify($user, $request->input('credential'))) {
// Authenticate user (e.g., via Laravel's auth system)
Auth::login($user);
}
Integration with Laravel Auth:
public function handle($request, Closure $next, $guard = null)
{
if ($request->has('passkey_credential')) {
$user = User::find($request->user_id);
if (Passkey::verify($user, $request->passkey_credential)) {
Auth::guard($guard)->login($user);
}
}
return $next($request);
}
$user->passkeys()->get(); // Returns a collection of Passkey models
$passkey = $user->passkeys()->find($id);
$passkey->delete();
@foreach($user->passkeys as $passkey)
<div>
{{ $passkey->name }} ({{ $passkey->created_at->diffForHumans() }})
<form action="{{ route('passkeys.revoke') }}" method="POST">
@csrf
<input type="hidden" name="passkey_id" value="{{ $passkey->id }}">
<button type="submit">Revoke</button>
</form>
</div>
@endforeach
if ($request->wantsJson() && Passkey::isSupported()) {
return response()->json(['use_passkey' => true]);
}
if (!window.PublicKeyCredential) {
// Show email/password form
}
Passkey facade’s testing helpers:
$passkey = Passkey::fake()->createForUser($user);
Passkey::shouldVerifyForUser($user, $passkey);
WebAuthn Browser Support:
Credential ID Collisions:
credentialID (e.g., from different devices), the second registration may overwrite the first.credentialID per passkey or implement a deduplication check in createForUser.Rate Limiting:
throttle middleware or a package like spatie/laravel-rate-limiting.Database Bloat:
publicKey, transports) can bloat the database.CORS Issues:
.env:
SESSION_DOMAIN=.yourdomain.com
SANCTUM_STATEFUL_DOMAINS=yourdomain.com
Verification Failures:
Passkey::verify() returns false, check:
credentialID matches a stored passkey.authenticatorData and signature are valid (use dd($request->all()) to inspect).id is correct (passkeys are scoped to users).config/passkeys.php:
'debug' => env('PASSKEYS_DEBUG', false),
WebAuthn Errors:
NotAllowedError) often indicate:
RP ID (relying party ID) in the challenge.user.id or user.name format.navigator.credentials.create().Event Listener Failures:
PasskeyRegisteredEvent isn't firing, verify:
EventServiceProvider.Relying Party (RP) ID:
rp.id in config/passkeys.php must match the domain where passkeys are used (e.g., example.com).env('APP_URL') to dynamically set it:
'rp' => [
'id' => parse_url(env('APP_URL'), PHP_URL_HOST),
'name' => config('app.name'),
],
User ID Format:
user.id must be a URL-safe string (e.g., UUID or hashed integer).Str::uuid() or hash the ID:
$user->passkey_user_id = hash('sha256', $user->id);
How can I help you explore Laravel packages today?