LaravelWebauthn is an adapter to use Webauthn as 2FA (two-factor authentication) or as passwordless authentication on Laravel.
Try this now on the demo application.
- Manage Webauthn keys registration
- 2nd factor authentication: add a middleware service to use a Webauthn key as 2FA
- Login provider using a Webauthn key, without password
Install LaravelWebauthn and a psr/http-factory-implementation implementation:
composer require asbiin/laravel-webauthn guzzlehttp/psr7You can use any other psr/http-factory-implementation implementation.
- We recommend using
guzzlehttp/psr7package:composer require guzzlehttp/psr7
- For instance you can use
nyholm/psr7. You'll also needsymfony/psr-http-message-bridgeandphp-http/discovery:composer require nyholm/psr7 symfony/psr-http-message-bridge php-http/discovery
You can publish LaravelWebauthn configuration in a file named config/webauthn.php, and resources using the vendor:publish command:
php artisan vendor:publish --provider="LaravelWebauthn\WebauthnServiceProvider"If desired, you may disable LaravelWebauthn entirely using the enabled configuration option:
'enabled' => false,Next, you should migrate your database:
php artisan migrateThe Webauthn middleware will force the user to authenticate their webauthn key for certain routes.
Add this in the $routeMiddleware array of your app/Http/Kernel.php file:
'webauthn' => \LaravelWebauthn\Http\Middleware\WebauthnMiddleware::class,You can use this middleware in your routes.php file:
Route::middleware(['auth', 'webauthn'])->group(function () {
Route::get('/home', 'HomeController@index')->name('home');
...
}The Webauthn middleware will redirect the user to the webauthn login page when required.
When session expires, but the user have set the remember token, you can revalidate webauthn session by adding this in your App\Providers\EventServiceProvider file:
use Illuminate\Auth\Events\Login;
use LaravelWebauthn\Listeners\LoginViaRemember;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
Login::class => [
LoginViaRemember::class,
],
];
// ...You can use Webauthn to authenticate a user without a password, using only a webauthn key authentication.
To enable passwordless authentication, first add the webauthn user provider: update your config/auth.php file and change the users provider:
'providers' => [
'users' => [
'driver' => 'webauthn',
'model' => App\Models\User::class,
],
],Then allow your login page to initiate a webauthn login with an email identifier.
You can call webauthn.auth.options route with a POST request and an email input to get the challenge data.
See authentication section for more details.
By default LaravelWebauthn defines routes that are intended to return views for authentication and register key.
However, if you are building a JavaScript driven single-page application, you may not need these routes. For that reason, you may disable these routes entirely by setting the views configuration value within your application's config/webauthn.php configuration file to false:
'views' => false,You will find an example of usage on asbiin/laravel-webauthn-example. You can try it right now on the demo application.
To authenticate with a webauthn key, the workflow is the following:
-
Open the
webauthn.loginlogin page. You can customize the login page view by callingWebauthn::loginViewResponseUsing. See View responseThe default behavior will open the webauthn::authenticate page. You can also change the value of
webauthn.views.authenticatein the configuration file. -
Or: Get the publicKey challenge by calling
webauthn.auth.options(if not provided). -
Start the webauthn browser authentication. You can use the
webauthn.jslibrary to do this.Send the signed data to
webauthn.authroute. -
The POST response will be:
- a redirect response
- or a json response with a
callbackdata.
Example:
<!-- load javascript part -->
<script src="{!! secure_asset('vendor/webauthn/webauthn.js') !!}"></script>
...
<!-- script part to run the sign part -->
<script>
var publicKey = {!! json_encode($publicKey) !!};
var webauthn = new WebAuthn();
webauthn.sign(
publicKey,
function (data) {
axios.post("{{ route('webauthn.auth') }}", data)
.then(function (response) {
if (response.data.callback) { window.location.href = response.data.callback;}
});
}
);
</script>If the authentication is successful, the server will use the webauthn.redirects.login configuration:
- to redirect the response on a plain http call
- or with a json response, like:
{ result: true, callback: `webauthn.redirects.login` target url, }
To register a new webauthn key, the workflow is the following:
-
Open the
webauthn.registerpage. You can customize the register page view by callingWebauthn::registerViewResponseUsing. See View responseThe default behavior will open the webauthn::register page. You can also change the value of
webauthn.views.registerin the configuration file. -
Or: Get the publicKey challenge by calling
webauthn.store.options(if not provided). -
Start the webauthn browser registration. You can use the
webauthn.jslibrary to do this.Send the signed data to
webauthn.storeroute. The data should contain anamefield with the webauthn key name. -
The POST response will be:
- a redirect response
- or a json response with a
callbackdata.
Example:
<!-- load javascript part -->
<script src="{!! secure_asset('vendor/webauthn/webauthn.js') !!}"></script>
...
<!-- script part to run the sign part -->
<script>
var publicKey = {!! json_encode($publicKey) !!};
var webauthn = new WebAuthn();
webauthn.register(
publicKey,
function (data) {
axios.post("{{ route('webauthn.store') }}", {
...data,
name: "{{ $name }}",
})
}
);
</script>If the registration is successful, the server will use the webauthn.redirects.register configuration:
- to redirect the response on a plain http call
- or with a json response, like:
{ result: json serialized webauthn key value, callback: `webauthn.redirects.register` target url, }
These routes are defined:
| Request | Route | Description |
|---|---|---|
GET /webauthn/auth |
webauthn.login |
The login page. |
POST /webauthn/auth/options |
webauthn.auth.options |
Get the publicKey and challenge to initiate a WebAuthn login. |
POST /webauthn/auth |
webauthn.auth |
Post data after a WebAuthn login validate. |
GET /webauthn/keys/create |
webauthn.create |
The register key page. |
POST /webauthn/keys/options |
webauthn.store.options |
Get the publicKeys and challenge to initiate a WebAuthn registration. |
POST /webauthn/keys |
webauthn.store |
Post data after a WebAuthn register check. |
DELETE /webauthn/keys/{id} |
webauthn.destroy |
Delete an existing key. |
PUT /webauthn/keys/{id} |
webauthn.update |
Update key properties (name, ...). |
You can customize the first part of the url by setting prefix value in the config file.
You can disable the routes creation by adding this in your AppServiceProvider:
use LaravelWebauthn\Services\Webauthn;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
Webauthn::ignoreRoutes();
}
}The Laravel Webauthn authentication pipeline is highly inspired by the Fortify pipeline.
If you would like, you may define a custom pipeline of classes that login requests should be piped through. Each class should have an __invoke method which receives the incoming Illuminate\Http\Request instance and, like middleware, a $next variable that is invoked in order to pass the request to the next class in the pipeline.
To define your custom pipeline, you may use the Webauthn::authenticateThrough method. This method accepts a closure which should return the array of classes to pipe the login request through. Typically, this method should be called from the boot method of your App\Providers\FortifyServiceProvider class.
The example below contains the default pipeline definition that you may use as a starting point when making your own modifications:
use LaravelWebauthn\Actions\AttemptToAuthenticate;
use LaravelWebauthn\Actions\EnsureLoginIsNotThrottled;
use LaravelWebauthn\Actions\PrepareAuthenticatedSession;
use LaravelWebauthn\Services\Webauthn;
use Illuminate\Http\Request;
Webauthn::authenticateThrough(function (Request $request) {
return array_filter([
config('webauthn.limiters.login') !== null ? null : EnsureLoginIsNotThrottled::class,
AttemptToAuthenticate::class,
PrepareAuthenticatedSession::class,
]);
});By default, Laravel Webauthn will throttle logins to five requests per minute for every email and IP address combination. You may specify a custom rate limiter with other specifications.
First define a custom rate limiter. Follow Laravel rate limiter documentation to create a new RateLimiter within the configureRateLimiting method of your application's App\Providers\RouteServiceProvider class.
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
/**
* Configure the rate limiters for the application.
*
* @return void
*/
protected function configureRateLimiting()
{
RateLimiter::for('webauthn-login', function (Request $request) {
return Limit::perMinute(1000);
});
}Then use this new custom rate limiter in your webauthn.limiters.login configuration:
'limiters' => [
'login' => 'webauthn-login',
],Events are dispatched by LaravelWebauthn:
\LaravelWebauthn\Events\WebauthnLoginon login with Webauthn check.\LaravelWebauthn\Events\WebauthnLoginDataon preparing authentication data challenge.\Illuminate\Auth\Events\Failedon a failed login check.\LaravelWebauthn\Events\WebauthnRegisteron registering a new key.\LaravelWebauthn\Events\WebauthnRegisterDataon preparing register data challenge.\LaravelWebauthn\Events\WebauthnRegisterFailedon failing registering a new key.
You can easily change the view responses with the Webauthn service.
For instance, call Webauthn::loginViewResponseUsing in your AppServiceProvider:
use LaravelWebauthn\Services\Webauthn;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
Webauthn::loginViewResponseUsing(LoginViewResponse::class);
}
}With a LoginViewResponse class:
use LaravelWebauthn\Http\Responses\LoginViewResponse as LoginViewResponseBase;
class LoginViewResponse extends LoginViewResponseBase
{
public function toResponse($request)
{
$publicKey = $this->publicKeyRequest($request);
return Inertia::render('Webauthn/WebauthnLogin', [
'publicKey' => $publicKey
]);
}
}List of methods and their expected response contracts:
| Webauthn static methods | \LaravelWebauthn\Contracts |
|---|---|
loginViewResponseUsing |
LoginViewResponseContract |
loginSuccessResponseUsing |
LoginSuccessResponseContract |
registerViewResponseUsing |
RegisterViewResponseContract |
registerSuccessResponseUsing |
RegisterSuccessResponseContract |
destroyViewResponseUsing |
DestroyResponseContract |
updateViewResponseUsing |
UpdateResponseContract |
This package has the following Laravel compatibility:
| Laravel | asbiin/laravel-webauthn |
|---|---|
| 5.8-8.x | <= 1.2.0 |
| 7.x-8.x | 2.0.1 |
| 9.x-10.x | >= 3.0.0 |
Most of the browsers support Webauthn.
However, your browser will refuse to negotiate a relay to your security device without the following:
- a proper domain (localhost and 127.0.0.1 will be rejected by
webauthn.js) - an SSL/TLS certificate trusted by your browser (self-signed is okay)
- connected HTTPS on port 443 (ports other than 443 will be rejected)
If you are a Laravel Homestead user, the default is to forward ports. You can switch from NAT/port forwarding to a private network with similar Homestead.yaml options:
sites:
- map: homestead.test
networks:
- type: "private_network"
ip: "192.168.254.2"Re-provisioning vagrant will inform your virtual machine of the new network and install self-signed SSL/TLS certificates automatically: vagrant reload --provision
If you haven't done so already, describe your site domain and network in your hosts file:
192.168.254.2 homestead.test
Author: Alexis Saettler
Copyright © 2019–2023.
Licensed under the MIT License. View license.