Password-based authentication is a legacy security model. Phishing, credential stuffing, and weak password management continue to be the primary attack vectors for modern web applications.
To solve this, the technology industry has rallied around WebAuthn and Passkeys—a cryptographic standard enabling users to log in using biometric sensors (such as TouchID or FaceID), PINs, or physical security keys.
In mid-2026, Laravel made this standard accessible by releasing laravel/passkeys on the backend, alongside @laravel/passkeys on the client.
Here is how to set up the official Laravel passkeys stack in your application.
1. How Passkeys Work Under the Hood
Unlike passwords, passkeys rely on public-key cryptography:
- Registration: The user's device generates a unique cryptographic key pair. The private key remains securely stored on the device's hardware enclave, while the public key is sent to the Laravel application.
- Authentication: The Laravel server sends a challenge. The device signs this challenge using its private key (after biometric verification) and returns it. The server verifies the signature using the stored public key.
At no point does the server store or receive private keys, biometrics, or passwords.
2. Server-Side Installation
Start by pulling in the first-party server-side package:
composer require laravel/passkeys
Next, publish the database migrations:
php artisan vendor:publish --tag=passkeys-migrations
Run the migrations to create the passkeys table, which will store the public credentials associated with your users:
php artisan migrate
Implementing the User Contract
Your User model must implement the PasskeyUser contract and utilize the PasskeyAuthenticatable trait:
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passkeys\Contracts\PasskeyUser;
use Laravel\Passkeys\Traits\PasskeyAuthenticatable;
class User extends Authenticatable implements PasskeyUser
{
use PasskeyAuthenticatable;
// ...
}
3. Integrating with Laravel Fortify
If you use Laravel Fortify or Jetstream, passkey support can be enabled directly within config/fortify.php under the features array:
'features' => [
Features::registration(),
Features::resetPasswords(),
Features::passkeys(), // Enable official passkey authentication
],
4. Frontend Integration with the JS SDK
To handle the browser-level WebAuthn ceremonies, Laravel provides a companion NPM library. Install it via your package manager:
npm install @laravel/passkeys
Registering a New Passkey
To allow a logged-in user to create a new passkey, import the registration helper and call it. Here is an implementation using Vanilla JS:
import { registerPasskey } from '@laravel/passkeys';
const createPasskeyButton = document.getElementById('create-passkey');
createPasskeyButton.addEventListener('click', async () => {
try {
// 1. Request options from the Laravel backend
const response = await fetch('/passkeys/register-options');
const options = await response.json();
// 2. Trigger biometric verification on the device
const credential = await registerPasskey(options);
// 3. Send the public key credential back to Laravel
const saveResponse = await fetch('/passkeys/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify(credential)
});
if (saveResponse.ok) {
alert('Passkey registered successfully!');
}
} catch (error) {
console.error('Passkey registration failed:', error);
}
});
Authenticating a User
To log a user in without a password, implement the login flow:
import { authenticatePasskey } from '@laravel/passkeys';
const loginButton = document.getElementById('login-passkey');
loginButton.addEventListener('click', async () => {
const email = document.getElementById('email').value;
try {
// 1. Fetch authentication challenge for this email
const response = await fetch('/passkeys/login-options', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
});
const options = await response.json();
// 2. Perform WebAuthn browser challenge
const assertion = await authenticatePasskey(options);
// 3. Submit assertion to login route
const loginResponse = await fetch('/passkeys/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify(assertion)
});
if (loginResponse.ok) {
window.location.href = '/dashboard';
}
} catch (error) {
console.error('Passkey login failed:', error);
}
});
5. Security and Operational Edge Cases
Cross-Device Syncing
Modern operating systems (such as Apple iCloud Keychain or Google Password Manager) sync passkeys across a user's devices. A passkey created on an iPhone is automatically available on a MacBook. Your application does not need to handle this; the browser and operating system handle credential syncing natively.
Fallback Strategies
You should never enforce passkeys as the only authentication mechanism. Always offer standard fallback options (such as magic links or multi-factor TOTP codes) for users who might lose access to their primary security devices or are logging in from hardware that does not support biometrics.
Conclusion
Implementing passkey security was once a complex undertaking involving low-level binary parsing and extensive browser verification loops. With Laravel’s official passkey stack, you can deploy a zero-password login flow in a single afternoon.
If you are looking to secure your company's platforms, upgrade authentication layers, or integrate zero-trust security practices, let's connect.
Reach out on the Contact Page to schedule a security consultation.