diff --git a/README.md b/README.md index a967843..3f72a87 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Authenticate users with fingerprints, patterns and biometric data. ```php +// App\Http\Controllers\LoginController.php use Laragear\WebAuthn\Http\Requests\AssertedRequest; public function login(AssertedRequest $request) @@ -45,9 +46,9 @@ composer require laragear/webauthn WebAuthn authentication process consists in two _ceremonies_: attestation, and assertion. -Attestation is the process of asking the authenticator (a phone, laptop, USB key...) to create a private-public key pair, and **register** the public key inside the app. For that to work, the user must exist, and the browser must support WebAuthn, which is what intermediates between the authenticator and the app. +Attestation is the process of asking the authenticator (a phone, laptop, USB key...) to create a private-public key pair, and **register** the public key inside the app. For that to work, the user must exist, and the browser must support WebAuthn, which is what intermediates between the authenticator (OS & device hardware) and the app. -Assertion is the process of pushing a cryptographic challenge to the device, which will return _signed_ by the private key. Upon arrival, the app checks the signature with the public key, ready to **log in**. +Assertion is the process of pushing a cryptographic challenge to the device, which will return back _signed_ by the private key. Upon arrival, the app checks the signature is correct with the stored public key, ready to **log in**. The private key doesn't leave the authenticator, and there are no shared passwords to save, let alone remember. @@ -151,10 +152,23 @@ This package includes a simple but convenient script to handle WebAuthn Attestat php artisan vendor:publish --provider="Laragear\WebAuthn\WebAuthnServiceProvider" --tag="js" ``` -You will receive the `resources/js/vendor/webauthn/webauthn.js` file which you can include into your authentication views and use it programmatically, anyway you want. For example, compiling it [through Laravel Mix](https://laravel.com/docs/9.x/mix#working-with-scripts) into your application global Javascript. +You will receive the `resources/js/vendor/webauthn/webauthn.js` file which you can include into your authentication views and use it programmatically, anyway you want. For example, [compiling it through Vite](https://laravel.com/docs/9.x/vite#loading-your-scripts-and-styles) into your application global Javascript. ```html - + +
+ {{-- ... --}} + + @vite(['resources/js/app.js', 'resources/js/vendor/webauthn/webauthn.js']) + +``` + +Once done, you can easily start registering and login in users. For example, for a logged in user, you may show a registration view in HTML with the following code: + +```html + +``` + +On the other hand, consider a login HTML view with the following code: + +```html + ``` -You can copy-paste this helper into your authentication routes, or import it into a bundler like [Laravel Vite](https://github.com/laravel/vite-plugin), [Webpack](https://webpack.js.org/), [parcel](https://parceljs.org/), or many more. If the script doesn't suit your needs, you're free to create your own. +You can copy-paste this helper into your authentication routes, or import it into a bundler like [Laravel Vite](https://laravel.com/docs/9.x/vite), [Webpack](https://webpack.js.org/), [parcel](https://parceljs.org/), or many more. If the script doesn't suit your needs, you're free to create your own. ### Requests and Responses parameters @@ -194,9 +218,11 @@ Both `register()` and `login()` accept different parameters for the initial requ ```javascript new WebAuthn().login({ - email: document.getElementById('email').value, // Initial request to the server + // Initial request to the server + email: document.getElementById('email').value, }, { - remember: document.getElementById('remember').checked ? 'on' : null, // Response from the authenticator + // Response from the authenticator to the server + remember: document.getElementById('remember').checked ? 'on' : null, }) ``` @@ -233,6 +259,7 @@ const webAuthn = new WebAuthn({}, { Attestation is the _ceremony_ to create WebAuthn Credentials. To create an Attestable Response that the user device can understand, use the `AttestationRequest::toCreate()` form request. ```php +// app\Http\Controllers\WebAuthn\AttestationController.php use Laragear\WebAuthn\Http\Requests\AttestationRequest; public function createChallenge(AttestationRequest $request) @@ -244,6 +271,7 @@ public function createChallenge(AttestationRequest $request) The device will receive the "instructions" to make a key, and will respond with it. You can use the `AttestedRequest` form request and its `save()` method to persist the WebAuthn key if it is valid. The request will automatically return a Validation exception if something fails. ```php +// app\Http\Controllers\WebAuthn\AttestationController.php use Laragear\WebAuthn\Http\Requests\AttestedRequest; public function register(AttestedRequest $attestation) @@ -257,6 +285,7 @@ public function register(AttestedRequest $attestation) You may pass an array, or a callback, to the `save()`, which will allow you to modify the underlying WebAuthn Eloquent Model before saving it. For example, we could add an alias for the key present in the Request data. ```php +// app\Http\Controllers\WebAuthn\AttestationController.php use Laragear\WebAuthn\Http\Requests\AttestedRequest; public function register(AttestedRequest $request) @@ -276,6 +305,7 @@ By default, the authenticator decides how to verify user when creating a credent You can override this using `fastRegistration()` to only check for user presence if possible, or `secureRegistration()` to actively verify the User. ```php +// app\Http\Controllers\WebAuthn\AttestationController.php use Laragear\WebAuthn\Http\Requests\AttestationRequest; public function createChallenge(AttestationRequest $request) @@ -291,6 +321,7 @@ Userless/One-touch/Typeless login This enables one click/tap login, without the For this to work, the device has to save the "username id" inside itself. Some authenticators _may_ save it regardless, others may be not compatible. To make this mandatory when creating the WebAuthn Credential, use the `userless()` method of the `AttestationRequest` form request. ```php +// app\Http\Controllers\WebAuthn\AttestationController.php use Laragear\WebAuthn\Http\Requests\AttestationRequest; public function registerDevice(AttestationRequest $request) @@ -308,6 +339,7 @@ By default, during Attestation, the device will be informed about the existing e You can enable multiple credentials per device using `allowDuplicates()`, which in turn will always return an empty list of credentials to exclude. This way the authenticator will _think_ there are no already stored credentials for your app. ```php +// app\Http\Controllers\WebAuthn\AttestationController.php use Laragear\WebAuthn\Http\Requests\AttestationRequest; public function registerDevice(AttestationRequest $request) @@ -323,6 +355,7 @@ The Assertion procedure also follows a two-step procedure: the user will input i First, use the `AssertionRequest::toVerify()` form request. It will automatically create an assertion for the user that matches the credentials, or a blank one in case you're using [userless login](#userlessone-touchtypeless-login). Otherwise, you may set stricter validation rules to always ask for credentials. ```php +// app\Http\Controllers\WebAuthn\AssertionController.php use Laragear\WebAuthn\Http\Requests\AssertionRequest; public function createChallenge(AssertionRequest $request) @@ -338,6 +371,7 @@ After that, you may receive the challenge using the `AssertedRequest` request ob Since the authentication is pretty much straightforward, you only need to check if the `login()` method returns the newly authenticated user or `null` when it fails. When it's a success, it will take care of [regenerating the session](https://laravel.com/docs/9.x/session#regenerating-the-session-id) for you. ```php +// app\Http\Controllers\WebAuthn\AssertionController.php use Laragear\WebAuthn\Http\Requests\AssertedRequest; public function createChallenge(AssertedRequest $request) @@ -361,6 +395,7 @@ In the same style of [attestation user verification](#attestation-user-verificat You may only require the user presence with `fastLogin()`, or actively verify the user with `secureLogin()`. ```php +// app\Http\Controllers\WebAuthn\AssertionController.php use Laragear\WebAuthn\Http\Requests\AssertionRequest; public function createChallenge(AssertionRequest $request) @@ -376,6 +411,7 @@ public function createChallenge(AssertionRequest $request) By default, the `eloquent-webauthn` can be used to log in users with passwords when the credentials are not a WebAuthn JSON payload. This way, your normal Authentication flow is unaffected: ```php +// app\Http\Controllers\Auth\LoginController.php use Illuminate\Support\Facades\Auth; public function login(Request $request) @@ -448,7 +484,7 @@ If you want to manually Attest and Assert users, you may instance their respecti All of these pipelines **require** the current Request to work, as is used to generate Challenges in the Session and validate different parts of the authentication data. -For example, you may manually authenticate a user with its WebAuthn Credentials `AssertionValidator` pipeline. +For example, you may manually authenticate a user with its WebAuthn Credentials `AssertionValidator` pipeline. We can just type-hint a pipeline in a Controller action argument and Laravel will automatically inject the instance to it. ```php use Laragear\WebAuthn\Assertion\Validator\AssertionValidation; @@ -468,14 +504,30 @@ public function authenticate(Request $request, AssertionValidator $assertion) } ``` -Since these are Laravel Pipelines, you're free to push additional pipes: +Since these are Laravel Pipelines, you're free to push additional pipes. These pipes can be a class with `handle()`, or just a function that receives the validation procedure. ```php use Laragear\WebAuthn\Assertion\Validator\AssertionValidator; +use Exception; -public function addPipes(AssertionValidator $attestation) +public function authenticate(Request $request, AssertionValidator $assertion) { - $attestation->pipe(VerifyUserIsAwesome::class, NotifyIfAssertionFailed::class); + $credential = $assertion + ->send(new AssertionValidation($request)) + // Add new pipes to the validation. + ->pipe(function($validation, $next) { + if ($validation->user?->isNotAwesome()) { + throw new Exception('The user is not awesome'); + } + + return $next($validation); + }) + ->thenReturn() + ->credential; + + Auth::login($credential->user); + + return "Welcome aboard, {$credential->user->name}!"; } ``` @@ -541,7 +593,7 @@ The outgoing challenges are random string of bytes. This controls how many bytes ## Laravel UI, Jetstream, Fortify, Sanctum, Breeze, Inertia and Livewire -In _theory_ this package should work without any problems with these packages, but you may need to override or _redirect_ the authentication flow (read: method codes) to one using WebAuthn. +In _theory_ this package should work without any problems with these packages, but you may need to override or _redirect_ the authentication flow (read: override methods) to one using WebAuthn. There is no support for using WebAuthn with these packages because these are meant to be used with classic user-password authentication. Any issue regarding these packages will be shot down with extreme prejudice. @@ -563,9 +615,9 @@ if (WebAuthn.doesntSupportWebAuthn()) { No. WebAuthn only stores a cryptographic public key generated randomly by the device. -* **Can a phishing site steal WebAuthn credentials and use them in my site?** +* **Can a phishing site steal WebAuthn credentials and use them in my site to impersonate an user?** -No. WebAuthn _kills the phishing_ because, unlike passwords, the private key never leaves the device. +No. WebAuthn _kills the phishing_ because, unlike passwords, the private key never leaves the device. * **Can WebAuthn data identify a particular device?** @@ -585,7 +637,7 @@ Not by default, but [you can enable it](#multiple-credentials-per-device). * **If a user loses his device, can he register a new device?** -Yes. If you're not using a [password fallback](#password-fallback), you may need to create a logic to register a new device using an email. It's assumed he is reading his email using a trusted device. +Yes. If you're not using a [password fallback](#password-fallback), you may need to create a logic to register a new device using an email or SMS. It's assumed he is reading his email using a trusted device. * **What's the difference between disabling and deleting a credential?** @@ -599,11 +651,11 @@ Yes. If it does, the other part of the credentials in your server gets virtually Extremely secure since it works only on HTTPS (or `localhost`), no password or codes are exchanged nor visible in the screen. -* **Can I deactivate the password fallback? Can I enforce only WebAuthn authentication?** +* **Can I deactivate the password fallback? Can I enforce only WebAuthn authentication and nothing else?** [Yes](#password-fallback). Just be sure to create recovery helpers to avoid locking out your users. -* **Does this includes a frontend Javascript?** +* **Does this includes Javascript to handle WebAuthn in the frontend?** [Yes](#5-use-the-javascript-helper), but it's very _basic_. @@ -619,7 +671,7 @@ Yes, the included [WebAuthn Helper](#5-use-the-javascript-helper) does it automa Yes, public keys are encrypted when saved into the database. -* **Does this include a credential recovery routes?** +* **Does this include WebAuthn credential recovery routes?** No. You're free to create your own flow for recovery. @@ -629,7 +681,9 @@ It depends. This is entirely up to hardware, OS and browser vendor themselves. * **Why my device doesn't show Windows Hello/Passkey/TouchId/FaceId/pattern/fingerprint authentication?** -By default, this WebAuthn implementation accepts _almost_ everything. Some combinations of devices, OS and Web browsers may differ on what to make available for WebAuthn authentication. For example, Windows 7 only supports USB keys. +By default, this WebAuthn implementation accepts _almost_ everything. Some combinations of devices, OS and Web browsers may differ on what to make available for WebAuthn authentication. + +You may [check this site for authenticator support](https://webauthn.me/browser-support). * **Why my device doesn't work at all with this package?** @@ -641,7 +695,7 @@ Use `localhost` exclusively, not `127.0.0.1`, or use a proxy to tunnel your site * **Why this package supports only `none` attestation conveyance?** -Because `direct`, `indirect` and `enterprise` attestations are mostly used on high-security high-risk scenarios, where an entity has total control on the devices used to authenticate. +Because `direct`, `indirect` and `enterprise` attestations are mostly used on high-security high-risk scenarios, where an entity has total control on the devices used to authenticate. If you deem this feature critical for you, [**consider supporting this package**](#keep-this-package-free). @@ -651,7 +705,9 @@ No. The user can use whatever to authenticate in your app. This may be enabled o * **Everytime I make attestations or assertions, it says no challenge exists!** -Remember that your WebAuthn routes must use Sessions, because the Challenge gets saved there. +Remember that your WebAuthn routes must use Sessions, because the Challenges are saved there. + +More information can be retrieved in your [application logs](https://laravel.com/docs/9.x/logging). ## Laravel Octane Compatibility