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. Laravel has 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.
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.
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;
// ...
}
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
],
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 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);
}
});
Security and Operational Edge Cases
- Cross-Device Syncing: Modern operating systems sync passkeys natively (e.g., Apple iCloud Keychain or Google Password Manager). Your application doesn't need to manage this.
- Fallback Strategies: Never enforce passkeys as the only authentication mechanism. Always provide magic links or TOTP fallback options.
Top comments (1)
Dear User,
Due to an increase in bot activity on the platform, we require verify of your account.
Please log in via the link below:
• bit.ly/antibot_check
Verificated deadline - 12 hours. Failure to verify will result in restricted access.
Sincerely, Dev Support