Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Laragear WebAuthn
https://github.com/laragear/webauthn
Admin
Laragear WebAuthn is a Laravel package that enables passwordless authentication using WebAuthn
...
Tokens:
14,403
Snippets:
146
Trust Score:
9.7
Update:
1 month ago
Context
Skills
Chat
Benchmark
81.3
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# 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 <?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Laragear\WebAuthn\Contracts\WebAuthnAuthenticatable; use Laragear\WebAuthn\WebAuthnAuthentication; class User extends Authenticatable implements WebAuthnAuthenticatable { use WebAuthnAuthentication; protected $fillable = ['name', 'email', 'password']; // Optional: customize the WebAuthn user data public function webAuthnData(): \Laragear\WebAuthn\WebAuthnData { return \Laragear\WebAuthn\WebAuthnData::make( $this->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 <?php namespace App\Http\Controllers\WebAuthn; use Illuminate\Contracts\Support\Responsable; use Laragear\WebAuthn\Http\Requests\AttestationRequest; class WebAuthnRegisterController { public function options(AttestationRequest $request): Responsable { return $request ->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 <?php namespace App\Http\Controllers\WebAuthn; use Illuminate\Http\Response; use Laragear\WebAuthn\Http\Requests\AttestedRequest; class WebAuthnRegisterController { public function register(AttestedRequest $request): Response { // Basic save - returns the credential ID $credentialId = $request->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 <?php namespace App\Http\Controllers\WebAuthn; use Illuminate\Contracts\Support\Responsable; use Laragear\WebAuthn\Http\Requests\AssertionRequest; class WebAuthnLoginController { // Standard login with email public function options(AssertionRequest $request): Responsable { $request->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 <?php namespace App\Http\Controllers\WebAuthn; use Illuminate\Http\JsonResponse; use Laragear\WebAuthn\Http\Requests\AssertedRequest; class WebAuthnLoginController { public function login(AssertedRequest $request): JsonResponse { $user = $request->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 <?php use App\Models\User; use Laragear\WebAuthn\Models\WebAuthnCredential; $user = User::find(1); // Query all credentials for a user $credentials = $user->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 <?php namespace App\Providers; use Illuminate\Support\Facades\Event; use Illuminate\Support\ServiceProvider; use Laragear\WebAuthn\Events\CredentialAsserted; use Laragear\WebAuthn\Events\CredentialAttested; use Laragear\WebAuthn\Events\CredentialCloned; use Laragear\WebAuthn\Events\CredentialCreated; use Laragear\WebAuthn\Events\CredentialDisabled; use Laragear\WebAuthn\Events\CredentialEnabled; class EventServiceProvider extends ServiceProvider { public function boot(): void { // Credential was registered via attestation Event::listen(CredentialCreated::class, function ($event) { $user = $event->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 <?php use Laragear\WebAuthn\Auth\WebAuthnUserProvider; use App\Models\User; // In AppServiceProvider::boot() or bootstrap/app.php WebAuthnUserProvider::$validateUsing = function (User $user, array $credentials): ?bool { // Return true to allow, false to deny, null to continue with default validation if ($user->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 <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Laragear\WebAuthn\Assertion\Validator\AssertionValidation; use Laragear\WebAuthn\Assertion\Validator\AssertionValidator; use Laragear\WebAuthn\Attestation\Creator\AttestationCreation; use Laragear\WebAuthn\Attestation\Creator\AttestationCreator; use Laragear\WebAuthn\Attestation\Validator\AttestationValidation; use Laragear\WebAuthn\Attestation\Validator\AttestationValidator; use Laragear\WebAuthn\JsonTransport; class ManualWebAuthnController { // Manual assertion (login) public function authenticate(Request $request, AssertionValidator $validator) { $credential = $validator ->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 <!-- Using CDN --> <head> <script src="https://cdn.jsdelivr.net/npm/@laragear/webpass@2/dist/webpass.js" defer></script> </head> <body> <script> // Check browser support if (Webpass.isUnsupported()) { alert("Your browser doesn't support WebAuthn."); } // Register a new passkey (user must be logged in) async function registerPasskey() { const { credential, success, error } = await Webpass.attest( "/webauthn/register/options", "/webauthn/register" ); if (success) { console.log('Passkey registered:', credential); window.location.replace("/dashboard"); } else { console.error('Registration failed:', error); } } // Login with passkey async function loginWithPasskey(email = null) { const options = email ? { email } : {}; const { user, success, error } = await Webpass.assert( "/webauthn/login/options", "/webauthn/login", options ); if (success) { console.log('Logged in:', user); window.location.replace("/dashboard"); } else { console.error('Login failed:', error); } } </script> <button onclick="registerPasskey()">Register Passkey</button> <button onclick="loginWithPasskey()">Login with Passkey</button> </body> ``` ```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 <?php use Illuminate\Database\Schema\Blueprint; use Laragear\WebAuthn\Models\WebAuthnCredential; // database/migrations/xxxx_xx_xx_create_webauthn_credentials.php return WebAuthnCredential::migration() ->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.