# Laragear WebAuthn Laragear WebAuthn is a Laravel package that enables passwordless authentication using WebAuthn/Passkeys. It allows users to authenticate using biometric data (fingerprints, facial recognition), hardware security keys, or device patterns instead of traditional passwords. The package implements the W3C WebAuthn 2.0 specification and integrates seamlessly with Laravel's authentication system. The package works through two ceremonies: attestation (registering a new credential/passkey) and assertion (authenticating with an existing credential). It provides form requests for handling both ceremonies, a custom Eloquent user provider that extends Laravel's authentication, automatic credential management, and cloned credential detection. The package supports both traditional password fallback and passwordless-only authentication modes. ## Installation Install the package via Composer and run the installation command to publish configuration, routes, and migrations. ```bash composer require laragear/webauthn php artisan webauthn:install php artisan migrate ``` ## Configure Auth Provider The WebAuthn driver extends Laravel's Eloquent User Provider. Configure it in `config/auth.php` to enable WebAuthn authentication alongside optional password fallback. ```php // config/auth.php return [ 'providers' => [ 'users' => [ 'driver' => 'eloquent-webauthn', 'model' => App\Models\User::class, 'password_fallback' => true, // Allow password login as fallback ], ] ]; ``` ## Implement WebAuthnAuthenticatable Contract Add the `WebAuthnAuthenticatable` contract and `WebAuthnAuthentication` trait to your User model to enable WebAuthn credential management. ```php email, $this->name ?? 'User' ); } } ``` ## Register WebAuthn Routes Register the default WebAuthn routes in your `routes/web.php` file. The routes handle both attestation (registration) and assertion (login) ceremonies. ```php // routes/web.php use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; use Laragear\WebAuthn\Http\Routes as WebAuthnRoutes; // Register default routes: POST /webauthn/register/options, POST /webauthn/register, // POST /webauthn/login/options, POST /webauthn/login WebAuthnRoutes::register()->withoutMiddleware(VerifyCsrfToken::class); // Or customize the paths and controllers WebAuthnRoutes::register( attest: 'auth/passkey/register', attestController: 'App\Http\Controllers\PasskeyRegisterController', assert: 'auth/passkey/login', assertController: 'App\Http\Controllers\PasskeyLoginController', )->withoutMiddleware(VerifyCsrfToken::class); ``` ## AttestationRequest - Create Registration Challenge Use `AttestationRequest` in your controller to generate a challenge for registering a new credential. The form request requires an authenticated user. ```php fastRegistration() // Only check user presence (not full verification) // ->secureRegistration() // Require full user verification (biometrics/PIN) // ->userless() // Enable one-touch/passwordless login // ->allowDuplicates() // Allow multiple credentials per device ->toCreate(); } // With custom user data callback public function optionsWithCustomData(AttestationRequest $request): Responsable { return $request ->using(function ($user, bool $usesUniqueCredentials) { $counter = $usesUniqueCredentials ? '' : ' #' . ($user->webAuthnCredentials()->whereEnabled()->count() + 1); return \Laragear\WebAuthn\WebAuthnData::make( name: $user->email, displayName: $user->name . $counter ); }) ->toCreate(); } } ``` ## AttestedRequest - Save New Credential Use `AttestedRequest` to validate and save the credential returned by the authenticator device after registration. ```php save(); return response()->json([ 'message' => 'Passkey registered successfully', 'credential_id' => $credentialId ]); } // Save with additional attributes public function registerWithAlias(AttestedRequest $request): Response { $request->validate(['alias' => 'nullable|string|max:255']); // Save with array of attributes $credentialId = $request->save($request->only('alias')); // Or use a callback for more control $credentialId = $request->save(function ($credential) use ($request) { $credential->alias = $request->input('alias', 'My Passkey'); }); return response()->json([ 'message' => 'Passkey registered', 'credential_id' => $credentialId ]); } } ``` ## AssertionRequest - Create Login Challenge Use `AssertionRequest` to generate a challenge for authenticating with an existing credential. Supports both userless login and user-specific login. ```php validate(['email' => 'sometimes|email|string']); return $request // ->fastLogin() // Only check user presence // ->secureLogin() // Require full user verification ->toVerify($request->only('email')); } // Userless/one-touch login (no email required) public function optionsUserless(AssertionRequest $request): Responsable { return $request->toVerify(); // Returns blank assertion for userless login } // Login with specific guard public function optionsWithGuard(AssertionRequest $request): Responsable { return $request ->guard('admin') ->toVerify($request->input('email')); } // Login by user ID instead of credentials public function optionsByUserId(AssertionRequest $request): Responsable { return $request->toVerify($request->input('user_id')); // Pass int or string ID } } ``` ## AssertedRequest - Authenticate User Use `AssertedRequest` to validate the signed challenge and log in the user. Automatically regenerates the session on successful login. ```php login(); if ($user) { return response()->json([ 'message' => "Welcome back, {$user->name}!", 'user' => $user->only(['id', 'name', 'email']) ]); } return response()->json(['message' => 'Authentication failed'], 422); } // Login with options public function loginWithOptions(AssertedRequest $request): JsonResponse { $user = $request->login( guard: 'web', remember: true, // Or null to use X-WebAuthn-Remember header destroySession: false, // Destroy other sessions callbacks: fn($user) => $user->isActive() // Additional validation ); return response()->json([ 'success' => (bool) $user, 'user' => $user?->only(['id', 'name', 'email']) ]); } // Login with multiple validation callbacks public function loginWithCallbacks(AssertedRequest $request): JsonResponse { $user = $request->login(callbacks: [ fn($user) => $user->isNotBanned(), fn($user) => $user->hasVerifiedEmail(), fn($user) => $user->subscription()->active(), ]); return response()->json(['authenticated' => (bool) $user]); } } ``` ## WebAuthnCredential Model - Managing Credentials The `WebAuthnCredential` model provides methods to query, enable, disable, and manage user credentials. ```php webAuthnCredentials()->get(); // Query only enabled credentials $enabledCredentials = $user->webAuthnCredentials()->whereEnabled()->get(); // Query only disabled credentials $disabledCredentials = $user->webAuthnCredentials()->whereDisabled()->get(); // Find a specific credential $credential = WebAuthnCredential::find('credential-id-string'); // Check credential status $credential->isEnabled(); // true or false $credential->isDisabled(); // true or false // Enable/disable a credential $credential->disable(); // Fires CredentialDisabled event $credential->enable(); // Fires CredentialEnabled event // Disable all credentials for a user (except specific ones) $user->disableAllCredentials(); $user->disableAllCredentials('keep-this-credential-id'); // Delete all credentials for a user (except specific ones) $user->flushCredentials(); $user->flushCredentials('keep-this-credential-id', 'and-this-one'); // Access credential properties $credential->id; // Credential ID (string) $credential->alias; // User-friendly name (nullable) $credential->origin; // Origin domain $credential->aaguid; // Authenticator AAGUID $credential->attestation_format; // Attestation format used $credential->disabled_at; // Timestamp when disabled (nullable) $credential->authenticatable; // Related user model ``` ## Events - Credential Lifecycle Listen to WebAuthn events to react to credential changes and security alerts. ```php user; $credential = $event->credential; // Send notification about new passkey registered }); // Credential was used to login Event::listen(CredentialAsserted::class, function ($event) { $credential = $event->credential; // Log successful WebAuthn login }); // Credential attestation completed Event::listen(CredentialAttested::class, function ($event) { $credential = $event->credential; }); // SECURITY: Cloned credential detected - auto-disabled Event::listen(CredentialCloned::class, function ($event) { $credential = $event->credential; $reportedCount = $event->reportedCount; // Alert user about potential security breach $credential->authenticatable->notify( new \App\Notifications\CredentialClonedAlert($credential) ); }); // Credential enabled/disabled Event::listen(CredentialEnabled::class, fn($e) => logger('Credential enabled: ' . $e->credential->id)); Event::listen(CredentialDisabled::class, fn($e) => logger('Credential disabled: ' . $e->credential->id)); } } ``` ## Custom Credential Validation Override the default credential validation logic using a custom callback for scenarios like LDAP integration. ```php prefersLdapAuth()) { return \Ldap\LdapAuth::attempt($user, $credentials); } if ($user->isSuspended()) { return false; } return null; // Continue with default WebAuthn validation }; ``` ## Manual Attestation and Assertion Pipelines For advanced scenarios, directly use the validation pipelines to manually attest or assert credentials. ```php send(AssertionValidation::fromRequest($request)) ->thenReturn() ->credential; Auth::login($credential->authenticatable); return response()->json([ 'message' => "Welcome, {$credential->authenticatable->name}!" ]); } // Manual assertion with custom pipe public function authenticateWithPipe(Request $request, AssertionValidator $validator) { $credential = $validator ->send(new AssertionValidation( new JsonTransport($request->all()) )) ->pipe(function ($validation, $next) { if ($validation->user?->isBanned()) { throw new \Exception('User is banned'); } return $next($validation); }) ->thenReturn() ->credential; Auth::login($credential->authenticatable); return response()->json(['success' => true]); } // Manual attestation options public function createOptions(AttestationCreator $creator) { $creation = new AttestationCreation(auth()->user()); return $creator ->send($creation) ->thenReturn() ->json; } // Manual attestation validation public function validateAttestation(Request $request, AttestationValidator $validator) { $credential = $validator ->send(new AttestationValidation( auth()->user(), new JsonTransport($request->all()) )) ->thenReturn() ->credential; // Modify credential before saving $credential->alias = $request->input('alias', 'My Device'); $credential->save(); return response()->json(['credential_id' => $credential->id]); } } ``` ## Configuration Options Customize WebAuthn behavior through environment variables or the published config file. ```php // config/webauthn.php return [ 'relying_party' => [ 'name' => env('WEBAUTHN_NAME', config('app.name')), 'id' => env('WEBAUTHN_ID'), // Domain like 'example.com' ], // Additional origins for Android apps or subdomains 'origins' => env('WEBAUTHN_ORIGINS'), // Comma-separated: 'app.example.com,android:apk-key-hash:...' 'challenge' => [ 'bytes' => 16, // Challenge length in bytes 'timeout' => 60, // Seconds before challenge expires 'key' => '_webauthn', // Session key for storing challenges ] ]; // .env WEBAUTHN_NAME=MySecureApp WEBAUTHN_ID=auth.example.com WEBAUTHN_ORIGINS=mobile.example.com,android:apk-key-hash:kffL-daBUxvHpY-4M8yhTavt5QnFEI2LsexohxrGPYU ``` ## JavaScript Frontend Integration Use the `@laragear/webpass` package or the built-in JavaScript helper for frontend WebAuthn handling. ```html ``` ```javascript // Using npm package import Webpass from "@laragear/webpass"; // Check support if (Webpass.isUnsupported()) { throw new Error("WebAuthn not supported"); } // Register with custom options const { credential, success } = await Webpass.attest( "/api/webauthn/register/options", "/api/webauthn/register", { alias: "Work Laptop" }, // Request data { "X-Custom-Header": "value" } // Custom headers ); // Login with remember option const { user, success } = await Webpass.assert( "/api/webauthn/login/options", "/api/webauthn/login", { email: "user@example.com" }, { "X-WebAuthn-Remember": "true" } ); ``` ## Migration Customization Customize the WebAuthn credentials table migration to add additional columns or relationships. ```php with(function (Blueprint $table) { // Add custom columns $table->string('device_name')->nullable(); $table->string('browser')->nullable(); $table->ipAddress('registered_ip')->nullable(); }) ->afterUp(function (Blueprint $table) { // Add indexes or foreign keys after table creation $table->index('device_name'); }) ->beforeDown(function (Blueprint $table) { // Clean up before dropping $table->dropIndex(['device_name']); }); // For UUID primary keys on user model, use morphUuid return WebAuthnCredential::migration()->morphUuid; // Or with custom index name return WebAuthnCredential::migration()->morph('uuid', 'webauthn_user_morph_index'); ``` Laragear WebAuthn is ideal for applications requiring passwordless authentication, two-factor authentication enhancement, or high-security login flows. Common use cases include banking applications, healthcare systems, enterprise portals, and any application where password-based authentication poses security risks. The package seamlessly integrates with existing Laravel authentication, allowing gradual migration from passwords to passkeys. Integration patterns typically involve adding WebAuthn as an optional authentication method alongside traditional passwords (using `password_fallback`), or implementing it as the primary authentication method for security-critical applications. The package works with Laravel's built-in guards, Sanctum for API authentication, and can be extended with custom validation pipelines for complex authentication requirements like LDAP integration or multi-tenancy support.