Zero-Password Security: Implementing the Official Laravel Passkeys Stack
DEV Community

Zero-Password Security: Implementing the Official Laravel Passkeys Stack

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.

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

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.