Installation:
composer require rawilk/laravel-webauthn
php artisan vendor:publish --tag="webauthn-migrations"
php artisan vendor:publish --tag="webauthn-config"
php artisan migrate
webauthn_credentials table) and config file.First Use Case:
use Rawilk\Webauthn\Webauthn;
$user = auth()->user();
$webauthn = new Webauthn($user);
$options = $webauthn->createRegistrationOptions();
return view('webauthn.register', compact('options'));
$result = $webauthn->verifyRegistrationResponse($request->all());
if ($result->isSuccess()) {
// Credential registered successfully
}
Key Files to Review:
config/laravel-webauthn.php: Configure RP (Relying Party) ID, origin, and security policies.resources/views/webauthn/: Default Blade views (extend/modify as needed).app/Models/WebauthnCredential.php: Model for storing credentials (extend for custom logic).$webauthn = new Webauthn($user);
$options = $webauthn->createRegistrationOptions();
WebauthnRegistrationOptions with challenge, rp, user, and pubKeyCredParams.@webauthnRegister($options)
publicKey options for the WebAuthn API.$result = $webauthn->verifyRegistrationResponse($request->all());
if ($result->isSuccess()) {
$credential = $result->getCredential();
$user->webauthnCredentials()->save($credential);
}
$webauthn = new Webauthn($user);
$options = $webauthn->createAssertionOptions();
@webauthnAuthenticate($options)
$result = $webauthn->verifyAssertionResponse($request->all());
if ($result->isSuccess()) {
// Authenticate user
auth()->login($user);
}
$credentials = $user->webauthnCredentials;
$credential = $user->webauthnCredentials()->find($id);
$credential->delete();
Extend LoginController:
public function authenticate(Request $request) {
$user = User::find($request->user_id);
$webauthn = new Webauthn($user);
$result = $webauthn->verifyAssertionResponse($request->all());
if ($result->isSuccess()) {
auth()->login($user);
return redirect()->intended('/dashboard');
}
return back()->withErrors(['auth' => 'Invalid credential']);
}
Add WebAuthn as a guard:
// config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
'webauthn' => true, // Custom flag to enable WebAuthn checks
],
];
Use the @webauthnRegister/@webauthnAuthenticate directives in Blade:
@webauthnRegister($options)
<script>
document.querySelector('webauthn-register-form').addEventListener('submit', async (e) => {
e.preventDefault();
const result = await navigator.credentials.create({ publicKey: @json($options) });
// Submit result to server
});
</script>
Customize the WebAuthn UI:
resources/views/webauthn/.WebauthnAssets helper to include custom CSS/JS:
WebauthnAssets::addJs('custom-webauthn.js');
Unit Test Verification:
public function test_registration_verification() {
$user = User::factory()->create();
$webauthn = new Webauthn($user);
$options = $webauthn->createRegistrationOptions();
// Mock a valid response
$response = [
'id' => 'test-credential-id',
'rawId' => 'base64-encoded-id',
'type' => 'public-key',
'response' => [
'attestationObject' => 'base64-attestation',
'clientDataJSON' => 'base64-client-data',
'transports' => ['usb', 'ble'],
],
];
$result = $webauthn->verifyRegistrationResponse($response);
$this->assertTrue($result->isSuccess());
}
Browser Testing:
CORS and Origin Configuration:
webauthn.rp.origin in the config matches your exact production domain (e.g., https://app.yourdomain.com).NotAllowedError during registration/authentication.origin in config/laravel-webauthn.php and ensure your server’s Host header matches.CSRF and WebAuthn:
webauthn.challenge_expiration to limit challenge validity (default: 60 seconds).Credential ID Collisions:
id field may collide.rawId (base64-encoded credential ID) and use it for deduplication:
$exists = WebauthnCredential::where('raw_id', $response['rawId'])->exists();
Browser/Device Compatibility:
localhost for development).Database Schema Assumptions:
webauthn_credentials table with specific fields (id, user_id, credential_id, public_key, raw_id, transports, counter).WebauthnCredential model if you need additional fields (e.g., name, last_used_at).Enable Verbose Logging:
webauthn.debug to true in the config to log challenges/responses.WebAuthnError: Invalid client data JSON
Validate Client Data:
clientDataJSON in the response must match the challenge you sent. Use:
$webauthn->verifyRegistrationResponse($request->all())->getErrors();
origin in clientDataJSON matches your config.Handle Attestation:
config(['webauthn.rp.id' => 'yourdomain.com']);
Counter Management:
counter field prevents replay attacks. If a credential’s counter is lower than the stored valueHow can I help you explore Laravel packages today?