Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Passkeys Laravel Package

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.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Installation**:
   ```bash
   composer require spatie/laravel-passkeys
   php artisan vendor:publish --provider="Spatie\Passkeys\PasskeysServiceProvider"
   php artisan migrate
  • Publishes config (config/passkeys.php) and creates the passkeys table.
  1. First Use Case:

    • Registration: Use the 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)));
      
    • Login: Verify a passkey during authentication:
      $user = User::find(1);
      $isValid = Passkey::verify($user, $request->input('passkey'));
      
  2. Frontend Integration:

    • Use the provided Blade directives (@passkeyCreate and @passkeyVerify) or JavaScript SDK (e.g., WebAuthn API) to interact with the browser’s passkey system.
    • Example Blade snippet for registration:
      @passkeyCreate('user', 'example.com', 'My App')
      

Implementation Patterns

Workflows

1. User Registration with Passkeys and Event Listening

  • Flow:

    1. User clicks "Register with Passkey" on the frontend.
    2. Frontend triggers the WebAuthn API to generate a new credential.
    3. Backend receives the credential, stores it, and fires an event:
      $user = User::create([...]);
      $passkey = Passkey::createForUser($user, $request->input('credential'));
      event(new \Spatie\Passkeys\Events\PasskeyRegisteredEvent($passkey));
      
    4. Listen for the event to trigger side effects (e.g., notifications, analytics):
      PasskeyRegisteredEvent::listen(function ($event) {
          Log::info("New passkey registered for user {$event->passkey->user_id}");
          // Send welcome email, update analytics, etc.
      });
      
    5. Store additional metadata (e.g., device name, icon) in the passkeys table.
  • Customization:

    • Extend the Spatie\Passkeys\Models\Passkey model to add custom fields (e.g., device_type, is_primary).
    • Override the createForUser method in a service class for additional logic.

2. User Login with Passkeys

  • Flow:

    1. User selects "Login with Passkey" and authenticates via WebAuthn.
    2. Backend verifies the credential:
      $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);
      }
      
    3. Optionally, revoke old passkeys or enforce single-device policies.
  • Integration with Laravel Auth:

    • Create a custom guard or use middleware to handle passkey authentication:
      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);
      }
      

3. Passkey Management (CRUD)

  • List User Passkeys:
    $user->passkeys()->get(); // Returns a collection of Passkey models
    
  • Revoke a Passkey:
    $passkey = $user->passkeys()->find($id);
    $passkey->delete();
    
  • UI Integration:
    • Use Blade to list and manage passkeys:
      @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
      

Integration Tips

  • Hybrid Authentication: Combine passkeys with traditional email/password login. Use middleware to check for passkey support:
    if ($request->wantsJson() && Passkey::isSupported()) {
        return response()->json(['use_passkey' => true]);
    }
    
  • Fallback for Unsupported Browsers: Detect WebAuthn support on the frontend and provide a fallback (e.g., email login):
    if (!window.PublicKeyCredential) {
        // Show email/password form
    }
    
  • Testing: Use the Passkey facade’s testing helpers:
    $passkey = Passkey::fake()->createForUser($user);
    Passkey::shouldVerifyForUser($user, $passkey);
    

Gotchas and Tips

Pitfalls

  1. WebAuthn Browser Support:

    • Passkeys rely on the WebAuthn API, which is not supported in older browsers (e.g., Safari < 15.4, IE/Edge Legacy).
    • Solution: Gracefully degrade to email/password or show a message like: "Your browser doesn’t support passkeys. Use your email and password instead."
  2. Credential ID Collisions:

    • If a user registers multiple passkeys with the same credentialID (e.g., from different devices), the second registration may overwrite the first.
    • Solution: Use a unique credentialID per passkey or implement a deduplication check in createForUser.
  3. Rate Limiting:

    • Passkey registration/login attempts should be rate-limited to prevent brute-force attacks.
    • Solution: Use Laravel’s throttle middleware or a package like spatie/laravel-rate-limiting.
  4. Database Bloat:

    • Storing raw credential data (e.g., publicKey, transports) can bloat the database.
    • Solution: Archive old passkeys or use a separate table for metadata.
  5. CORS Issues:

    • If your frontend and backend are on different domains, ensure CORS headers are configured to allow passkey requests.
    • Solution: Add to your .env:
      SESSION_DOMAIN=.yourdomain.com
      SANCTUM_STATEFUL_DOMAINS=yourdomain.com
      

Debugging

  • Verification Failures:

    • If Passkey::verify() returns false, check:
      • The credentialID matches a stored passkey.
      • The authenticatorData and signature are valid (use dd($request->all()) to inspect).
      • The user’s id is correct (passkeys are scoped to users).
    • Enable debug logging in config/passkeys.php:
      'debug' => env('PASSKEYS_DEBUG', false),
      
  • WebAuthn Errors:

    • Browser console errors (e.g., NotAllowedError) often indicate:
      • Missing RP ID (relying party ID) in the challenge.
      • Incorrect user.id or user.name format.
    • Fix: Ensure your frontend passes the correct parameters to navigator.credentials.create().
  • Event Listener Failures:

    • If PasskeyRegisteredEvent isn't firing, verify:
      • The event is properly registered in EventServiceProvider.
      • No exceptions are silently caught during passkey creation.

Config Quirks

  • Relying Party (RP) ID:

    • The rp.id in config/passkeys.php must match the domain where passkeys are used (e.g., example.com).
    • Tip: Use env('APP_URL') to dynamically set it:
      'rp' => [
          'id' => parse_url(env('APP_URL'), PHP_URL_HOST),
          'name' => config('app.name'),
      ],
      
  • User ID Format:

    • The user.id must be a URL-safe string (e.g., UUID or hashed integer).
    • Solution: Use Str::uuid() or hash the ID:
      $user->passkey_user_id = hash('sha256', $user->id);
      

Extension Points

  1. Custom Passkey Model:
    • Extend `Spatie
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport