Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b421096758 | |||
|
|
44ff0a94ad | ||
|
|
7e62ec927e | ||
|
|
872a7c667c | ||
|
|
24afe969f6 | ||
|
|
a4b074cdea | ||
|
|
37f99b2e6d | ||
|
|
26fb8e436f | ||
|
|
33f8de061a |
12
.github/workflows/php.yml
vendored
12
.github/workflows/php.yml
vendored
@@ -53,22 +53,23 @@ jobs:
|
|||||||
- "8.0"
|
- "8.0"
|
||||||
- "8.1"
|
- "8.1"
|
||||||
- "8.2"
|
- "8.2"
|
||||||
laravel-constrain:
|
laravel-constraint:
|
||||||
- "9.*"
|
- "9.*"
|
||||||
- "10.*"
|
- "10.*"
|
||||||
dependencies:
|
dependencies:
|
||||||
- "lowest"
|
- "lowest"
|
||||||
- "highest"
|
- "highest"
|
||||||
exclude:
|
exclude:
|
||||||
- laravel-constrain: "10.*"
|
- php-version: "8.0"
|
||||||
php-version: "8.0"
|
laravel-constraint: "10.*"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Set up PHP"
|
- name: "Set up PHP"
|
||||||
uses: "shivammathur/setup-php@v2"
|
uses: "shivammathur/setup-php@v2"
|
||||||
with:
|
with:
|
||||||
php-version: "${{ matrix.php-version }}"
|
php-version: "${{ matrix.php-version }}"
|
||||||
extensions: mbstring, intl
|
extensions: "mbstring, intl"
|
||||||
coverage: xdebug
|
coverage: "xdebug"
|
||||||
|
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: "actions/checkout@v3"
|
uses: "actions/checkout@v3"
|
||||||
@@ -77,6 +78,7 @@ jobs:
|
|||||||
uses: "ramsey/composer-install@v2"
|
uses: "ramsey/composer-install@v2"
|
||||||
with:
|
with:
|
||||||
dependency-versions: "${{ matrix.dependencies }}"
|
dependency-versions: "${{ matrix.dependencies }}"
|
||||||
|
composer-options: "--with=laravel/framework:${{ matrix.laravel-constraint }}"
|
||||||
|
|
||||||
- name: "Execute unit tests"
|
- name: "Execute unit tests"
|
||||||
run: "composer run-script test"
|
run: "composer run-script test"
|
||||||
|
|||||||
89
README.md
89
README.md
@@ -7,7 +7,7 @@
|
|||||||
[](https://sonarcloud.io/dashboard?id=Laragear_WebAuthn)
|
[](https://sonarcloud.io/dashboard?id=Laragear_WebAuthn)
|
||||||
[](https://laravel.com/docs/9.x/octane#introduction)
|
[](https://laravel.com/docs/9.x/octane#introduction)
|
||||||
|
|
||||||
Authenticate users with fingerprints, patterns and biometric data.
|
Authenticate users with Passkeys: fingerprints, patterns and biometric data.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
// App\Http\Controllers\LoginController.php
|
// App\Http\Controllers\LoginController.php
|
||||||
@@ -42,15 +42,15 @@ Require this package into your project using Composer:
|
|||||||
composer require laragear/webauthn
|
composer require laragear/webauthn
|
||||||
```
|
```
|
||||||
|
|
||||||
## How does it work?
|
## How Passkeys work?
|
||||||
|
|
||||||
WebAuthn authentication process consists in two _ceremonies_: attestation, and assertion.
|
Passkeys, hence WebAuthn, 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 (OS & device hardware) and the app.
|
Attestation is the process of asking the authenticator (a phone, laptop, USB key...) to create a private-public key pair, save the private key internally, and **store** the public key inside the server. For that to work, the browser must support WebAuthn, which is what intermediates between the authenticator (OS & device hardware) and the server.
|
||||||
|
|
||||||
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**.
|
Assertion is the process of pushing a cryptographic challenge to the authenticator, which will return back to the server _signed_ by the private key of the device. Upon arrival, the server 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.
|
The private key doesn't leave the authenticator, there are no shared passwords stored anywhere, and Passkeys only work on the server domain (like google.com) or subdomain (like auth.google.com).
|
||||||
|
|
||||||
## Set up
|
## Set up
|
||||||
|
|
||||||
@@ -65,6 +65,10 @@ After that, you can quickly start WebAuthn with the included controllers and hel
|
|||||||
4. [Register the controllers](#4-register-the-routes-and-controllers)
|
4. [Register the controllers](#4-register-the-routes-and-controllers)
|
||||||
5. [Use the Javascript helper](#5-use-the-javascript-helper)
|
5. [Use the Javascript helper](#5-use-the-javascript-helper)
|
||||||
|
|
||||||
|
> **Info**
|
||||||
|
>
|
||||||
|
> While you can use Passkeys without users by invoking the _ceremonies_ manually, Laragear WebAuthn is intended to be used with already existing Users.
|
||||||
|
|
||||||
### 1. Add the `eloquent-webauthn` driver
|
### 1. Add the `eloquent-webauthn` driver
|
||||||
|
|
||||||
Laragear WebAuthn works by extending the Eloquent User Provider with an additional check to find a user for the given WebAuthn Credentials (Assertion). This makes this WebAuthn package compatible with any guard you may have.
|
Laragear WebAuthn works by extending the Eloquent User Provider with an additional check to find a user for the given WebAuthn Credentials (Assertion). This makes this WebAuthn package compatible with any guard you may have.
|
||||||
@@ -85,7 +89,7 @@ return [
|
|||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
The `password_fallback` indicates the User Provider should fall back to validate the password when the request is not a WebAuthn Assertion. It's enabled to seamlessly use both classic and WebAuthn authentication procedures.
|
The `password_fallback` indicates the User Provider should fall back to validate the password when the request is not a WebAuthn Assertion. It's enabled to seamlessly use both classic (password) and WebAuthn authentication procedures.
|
||||||
|
|
||||||
### 2. Create the `webauthn_credentials` table
|
### 2. Create the `webauthn_credentials` table
|
||||||
|
|
||||||
@@ -152,25 +156,37 @@ This package includes a simple but convenient script to handle WebAuthn Attestat
|
|||||||
php artisan vendor:publish --provider="Laragear\WebAuthn\WebAuthnServiceProvider" --tag="js"
|
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 Vite](https://laravel.com/docs/9.x/vite#loading-your-scripts-and-styles) 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
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<head>
|
<head>
|
||||||
{{-- ... --}}
|
{{-- ... --}}
|
||||||
|
|
||||||
@vite(['resources/js/app.js', 'resources/js/vendor/webauthn/webauthn.js'])
|
<script src="{{ Vite::asset('resources/js/vendor/webauthn/webauthn.js') }}"></script>
|
||||||
|
|
||||||
|
@vite(['resources/js/app.js'])
|
||||||
</head>
|
</head>
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
> **Note**
|
||||||
|
>
|
||||||
|
> You can also edit the script file to transform it into a module so it can be bundled in your Vite frontend, exporting the class as `export default class WebAuthn { ... }`, and add it to the `@vite` assets.
|
||||||
|
>
|
||||||
|
> ```html
|
||||||
|
> @vite(['resources/js/vendor/webauthn/webauthn.js', 'resources/js/app.js'])
|
||||||
|
> ```
|
||||||
|
|
||||||
|
Once done, you can easily start registering an user device, and login in users that registered a device previusly.
|
||||||
|
|
||||||
|
For example, let's imagine an user logs in normally, and enters its profile view. You may show a WebAuthn registration HTML with the following code:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<form id="register-form">
|
<form id="register-form">
|
||||||
<button type="submit" value="Register authenticator">
|
<button type="submit" value="Register authenticator">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Registering credentials -->
|
<!-- Registering authenticator -->
|
||||||
<script>
|
<script>
|
||||||
const register = event => {
|
const register = event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -184,7 +200,7 @@ Once done, you can easily start registering and login in users. For example, for
|
|||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
On the other hand, consider a login HTML view with the following code:
|
In our Login view, we can use the WebAuthn credentials to log in the user.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<form id="login-form">
|
<form id="login-form">
|
||||||
@@ -210,7 +226,7 @@ On the other hand, consider a login HTML view with the following code:
|
|||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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, as long you adjust the script to the bundler needs. If the script doesn't suit your needs, you're free to modify it or create your own.
|
||||||
|
|
||||||
### Requests and Responses parameters
|
### Requests and Responses parameters
|
||||||
|
|
||||||
@@ -294,7 +310,12 @@ public function register(AttestedRequest $request)
|
|||||||
{
|
{
|
||||||
$request->validate(['alias' => 'nullable|string']);
|
$request->validate(['alias' => 'nullable|string']);
|
||||||
|
|
||||||
$attestation->save($request->input('alias'));
|
$attestation->save($request->only('alias'));
|
||||||
|
|
||||||
|
// Same as:
|
||||||
|
// $attestation->save(function ($credentials) use ($request) {
|
||||||
|
// $credentials->alias = $request->input('alias');
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -628,7 +649,9 @@ No. WebAuthn only stores a cryptographic public key generated randomly by the de
|
|||||||
|
|
||||||
* **Can a phishing site steal WebAuthn credentials and use them in my site to impersonate an user?**
|
* **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, and the key-pair is bound to the top-most domain it was registered.
|
||||||
|
|
||||||
|
An user bing _phished_ at `staetbank.com` won't be able to login with a key made on the legit site `statebank.com`, as the device won't be able to find it.
|
||||||
|
|
||||||
* **Can WebAuthn data identify a particular device?**
|
* **Can WebAuthn data identify a particular device?**
|
||||||
|
|
||||||
@@ -652,15 +675,15 @@ Yes. If you're not using a [password fallback](#password-fallback), you may need
|
|||||||
|
|
||||||
* **What's the difference between disabling and deleting a credential?**
|
* **What's the difference between disabling and deleting a credential?**
|
||||||
|
|
||||||
Disabling a credential doesn't delete it, so it's useful as a blacklisting mechanism and these can also be re-enabled. When the credential is deleted, it goes away forever.
|
Disabling a credential doesn't delete it, so it's useful as a blacklisting mechanism and these can also be re-enabled. When the credential is deleted, it goes away forever from the server, so the credential in the authenticator device becomes orphaned.
|
||||||
|
|
||||||
* **Can a user delete its credentials from its device?**
|
* **Can a user delete its credentials from its device?**
|
||||||
|
|
||||||
Yes. If it does, the other part of the credentials in your server gets virtually orphaned. You may want to show the user a list of registered credentials in the application to delete them.
|
Yes. If it does, the other part of the credentials in your server gets orphaned. You may want to show the user a list of registered credentials in the application to delete them.
|
||||||
|
|
||||||
* **How secure is this against passwords or 2FA?**
|
* **How secure is this against passwords or 2FA?**
|
||||||
|
|
||||||
Extremely secure since it works only on HTTPS (or `localhost`), no password or codes are exchanged nor visible in the screen.
|
Extremely secure since it works only on HTTPS (or `localhost`). Also, no password or codes are exchanged nor visible in the screen.
|
||||||
|
|
||||||
* **Can I deactivate the password fallback? Can I enforce only WebAuthn authentication and nothing else?**
|
* **Can I deactivate the password fallback? Can I enforce only WebAuthn authentication and nothing else?**
|
||||||
|
|
||||||
@@ -670,9 +693,13 @@ Extremely secure since it works only on HTTPS (or `localhost`), no password or c
|
|||||||
|
|
||||||
[Yes](#5-use-the-javascript-helper), but it's very _basic_.
|
[Yes](#5-use-the-javascript-helper), but it's very _basic_.
|
||||||
|
|
||||||
|
If you need more complex WebAuthn management, consider using the [`navigator.credentials`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/credentials) API directly.
|
||||||
|
|
||||||
* **Does WebAuthn eliminate bots? Can I forget about _captchas_?**
|
* **Does WebAuthn eliminate bots? Can I forget about _captchas_?**
|
||||||
|
|
||||||
No, you still need to use [captcha](https://github.com/Laragear/ReCaptcha), honeypots, or other mechanisms to stop bots.
|
Yes and no. To register users, you still need to use [captcha](https://github.com/Laragear/ReCaptcha), honeypots, or other mechanisms to stop bots.
|
||||||
|
|
||||||
|
Once a user is registered, bots won't be able to login because the real user is the only one that has the private key required for WebAuthn.
|
||||||
|
|
||||||
* **Does this encode/decode the WebAuthn data automatically in the frontend?**
|
* **Does this encode/decode the WebAuthn data automatically in the frontend?**
|
||||||
|
|
||||||
@@ -684,11 +711,15 @@ Yes, public keys are encrypted when saved into the database.
|
|||||||
|
|
||||||
* **Does this include WebAuthn credential recovery routes?**
|
* **Does this include WebAuthn credential recovery routes?**
|
||||||
|
|
||||||
No. You're free to create your own flow for recovery.
|
No. You're free to create your own flow for recovery.
|
||||||
|
|
||||||
|
My recommendation is to send an email to the user, pointing to a route that registers a new device, and immediately redirect him to blacklist which credential was lost (or blacklist the only one he has).
|
||||||
|
|
||||||
* **Can I use my smartphone as authenticator through my PC or Mac?**
|
* **Can I use my smartphone as authenticator through my PC or Mac?**
|
||||||
|
|
||||||
It depends. This is entirely up to hardware, OS and browser vendor themselves.
|
It depends.
|
||||||
|
|
||||||
|
This is entirely up to hardware, OS and browser vendor themselves, but mostly the OS. Some OS or browsers may offer a way to sync private keys on the cloud, even letting the assertion challenge to be signed remotely instead of transmitting the private key. Please check your target platforms of choice.
|
||||||
|
|
||||||
* **Why my device doesn't show Windows Hello/Passkey/TouchId/FaceId/pattern/fingerprint authentication?**
|
* **Why my device doesn't show Windows Hello/Passkey/TouchId/FaceId/pattern/fingerprint authentication?**
|
||||||
|
|
||||||
@@ -698,15 +729,17 @@ You may [check this site for authenticator support](https://webauthn.me/browser-
|
|||||||
|
|
||||||
* **Why my device doesn't work at all with this package?**
|
* **Why my device doesn't work at all with this package?**
|
||||||
|
|
||||||
This package supports WebAuthn 2.0, which is [W3C Recommendation](https://www.w3.org/TR/webauthn-2). Your device/OS/browser may be using an unsupported version. There are no plans to support older specs.
|
This package supports WebAuthn 2.0, which is [W3C Recommendation](https://www.w3.org/TR/webauthn-2). Your device/OS/browser may be using an unsupported version.
|
||||||
|
|
||||||
|
There are no plans to support older WebAuthn specs. The new [WebAuthn 3.0 draft](https://www.w3.org/TR/webauthn-3) spec needs to be finished to be supported.
|
||||||
|
|
||||||
* **I'm trying to test this in my development server, but it doesn't work**
|
* **I'm trying to test this in my development server, but it doesn't work**
|
||||||
|
|
||||||
Use `localhost` exclusively, not `127.0.0.1`, or use a proxy to tunnel your site through HTTPS. WebAuthn only works on `localhost` or under `HTTPS` only.
|
Use `localhost` exclusively (not `127.0.0.1` or `::1`) or use a proxy to tunnel your site through HTTPS. WebAuthn only works on `localhost` or under `HTTPS` only.
|
||||||
|
|
||||||
* **Why this package supports only `none` attestation conveyance?**
|
* **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. Imagine banks, medical, or military.
|
||||||
|
|
||||||
If you deem this feature critical for you, [**consider supporting this package**](#keep-this-package-free).
|
If you deem this feature critical for you, [**consider supporting this package**](#keep-this-package-free).
|
||||||
|
|
||||||
@@ -716,7 +749,7 @@ 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!**
|
* **Everytime I make attestations or assertions, it says no challenge exists!**
|
||||||
|
|
||||||
Remember that your WebAuthn routes must use Sessions, because the Challenges are saved there.
|
Remember that your WebAuthn routes **must use Sessions**, because the Challenges are stored there.
|
||||||
|
|
||||||
More information can be retrieved in your [application logs](https://laravel.com/docs/9.x/logging).
|
More information can be retrieved in your [application logs](https://laravel.com/docs/9.x/logging).
|
||||||
|
|
||||||
@@ -735,11 +768,11 @@ These are some details about this WebAuthn implementation:
|
|||||||
|
|
||||||
* Registration (attestation) and Login (assertion) challenges use the current request session.
|
* Registration (attestation) and Login (assertion) challenges use the current request session.
|
||||||
* Only one ceremony can be done at a time.
|
* Only one ceremony can be done at a time.
|
||||||
* Challenges are pulled from the session on resolution, independently of their result.
|
* Challenges are pulled (retrieved and deleted from source) from the session on resolution, independently of their result.
|
||||||
* All challenges and ceremonies expire at 60 seconds.
|
* All challenges and ceremonies expire at 60 seconds.
|
||||||
* WebAuthn User Handle is UUID v4, reusable if another credential exists.
|
* WebAuthn User Handle is UUID v4, reusable if another credential exists.
|
||||||
* Credentials can be blacklisted (enabled/disabled).
|
* Credentials can be blacklisted (enabled/disabled).
|
||||||
* Public Keys are encrypted in the database automatically.
|
* Public Keys are encrypted by with application key in the database automatically.
|
||||||
|
|
||||||
If you discover any security related issues, please email darkghosthunter@gmail.com instead of using the issue tracker.
|
If you discover any security related issues, please email darkghosthunter@gmail.com instead of using the issue tracker.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "laragear/webauthn",
|
"name": "kletellier/webauthn",
|
||||||
"description": "Authenticate your users with biometric data, devices or USB keys.",
|
"description": "Authenticate users with Passkeys: fingerprints, patterns and biometric data.",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
@@ -25,11 +25,16 @@
|
|||||||
"email": "DarkGhostHunter@Gmail.com",
|
"email": "DarkGhostHunter@Gmail.com",
|
||||||
"role": "Developer",
|
"role": "Developer",
|
||||||
"homepage": "https://github.com/sponsors/DarkGhostHunter"
|
"homepage": "https://github.com/sponsors/DarkGhostHunter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gregory Letellier",
|
||||||
|
"email": "register@gletellier.com",
|
||||||
|
"role": "Developer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/Laragear/TwoFactor",
|
"source": "https://github.com/Laragear/WebAuthn",
|
||||||
"issues": "https://github.com/Laragear/TwoFactor/issues"
|
"issues": "https://github.com/Laragear/WebAuthn/issues"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "8.*",
|
"php": "8.*",
|
||||||
|
|||||||
Reference in New Issue
Block a user