Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/asbiin/laravel-webauthn

Webauthn adapter for Laravel
https://github.com/asbiin/laravel-webauthn

authentication hacktoberfest laravel laravel-webauthn library php webauthn

Last synced: about 17 hours ago
JSON representation

Webauthn adapter for Laravel

Awesome Lists containing this project

README

        

Webauthn adapter for Laravel
============================

[![Latest Version](https://img.shields.io/packagist/v/asbiin/laravel-webauthn.svg?style=flat-square&label=Latest%20Version)](https://github.com/asbiin/laravel-webauthn/releases)
[![Downloads](https://img.shields.io/packagist/dt/asbiin/laravel-webauthn.svg?style=flat-square&label=Downloads)](https://packagist.org/packages/asbiin/laravel-webauthn)
[![Workflow Status](https://img.shields.io/github/actions/workflow/status/asbiin/laravel-webauthn/tests.yml?branch=main&style=flat-square&label=Workflow%20Status)](https://github.com/asbiin/laravel-webauthn/actions?query=branch%3Amain)
[![Quality Gate](https://img.shields.io/sonar/quality_gate/asbiin_laravel-webauthn?server=https%3A%2F%2Fsonarcloud.io&style=flat-square&label=Quality%20Gate)](https://sonarcloud.io/dashboard?id=asbiin_laravel-webauthn)
[![Coverage Status](https://img.shields.io/sonar/coverage/asbiin_laravel-webauthn?server=https%3A%2F%2Fsonarcloud.io&style=flat-square&label=Coverage%20Status)](https://sonarcloud.io/dashboard?id=asbiin_laravel-webauthn)

- [Features](#features)
- [Installation](#installation)
- [Configuration](#configuration)
- [Set Up](#set-up)
- [Option 1: add LaravelWebauthn middleware](#option-1-add-laravelwebauthn-middleware)
- [Login via remember](#login-via-remember)
- [Option 2: Passwordless authentication](#option-2-passwordless-authentication)
- [Disabling Views](#disabling-views)
- [Cache](#cache)
- [Usage](#usage)
- [Authenticate](#authenticate)
- [Register a new key](#register-a-new-key)
- [Routes](#routes)
- [Ignore route creation](#ignore-route-creation)
- [Customizing The Authentication Pipeline](#customizing-the-authentication-pipeline)
- [Rate Limiter](#rate-limiter)
- [Events](#events)
- [View response](#view-response)
- [Compatibility](#compatibility)
- [Laravel compatibility](#laravel-compatibility)
- [Browser compatibility](#browser-compatibility)
- [Homestead](#homestead)
- [License](#license)

**LaravelWebauthn** is the adapter to use Webauthn as [2FA](https://en.wikipedia.org/wiki/Multi-factor_authentication) (two-factor authentication) or as passwordless authentication on Laravel.

**Try this now on the [demo application](https://webauthn.asbin.net/).**

# Features

- 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

# Installation

Install this package with:

```sh
composer require asbiin/laravel-webauthn
```

## Configuration

You can publish LaravelWebauthn configuration in a file named `config/webauthn.php`, and resources using the `vendor:publish` command:

```sh
php artisan vendor:publish --provider="LaravelWebauthn\WebauthnServiceProvider"
```

Next, you should migrate your database:

```sh
php artisan migrate
```

# Set Up

## Option 1: add LaravelWebauthn middleware

The Webauthn middleware will force the user to authenticate their webauthn key for certain routes.

Assign the middleware to a route or a group of routes:

```php
use LaravelWebauthn\Http\Middleware\WebauthnMiddleware;

Route::get('/home', function () {
// ...
})->middleware(WebauthnMiddleware::class);
```

The Webauthn middleware will redirect the user to the webauthn login page when required.

### Login via remember

When session expires, but the user have set the `remember` cookie, you can revalidate webauthn session by subscribing to the `LaravelWebauthn\Listeners\LoginViaRemember` listener:

```php
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(
\Illuminate\Auth\Events\Login::class,
\LaravelWebauthn\Listeners\LoginViaRemember::class
);
}
}
```

## Option 2: Passwordless authentication

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:

```php
'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](#Authenticate) section for more details.

## Disabling Views

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:

```php
'views' => false,
```

## Cache

Note this package uses the cache to store the challenge data between the server request and the browser response. You'll need to setup a real cache driver from your `config/cache.php` file, and thus you can't use the `array` or `null` driver.

# Usage

You will find an example of usage on [asbiin/laravel-webauthn-example](https://github.com/asbiin/laravel-webauthn-example). You can try it right now on the [demo application](https://webauthn.asbin.net/).

## Authenticate

To authenticate with a webauthn key, the workflow is the following:
1. Open the `webauthn.login` login page.
You can customize the login page view by calling `Webauthn::loginViewResponseUsing`. See [View response](#view-response)

The default behavior will open the [webauthn::authenticate](/resources/views/authenticate.blade.php) page.
You can also change the value of `webauthn.views.authenticate` in the configuration file.

2. Or: Get the publicKey challenge by calling `webauthn.auth.options` (if not provided).

3. Start the webauthn browser authentication.
You can use the [`webauthn.js`](/resources/js/webauthn.js) library to do this.

Send the signed data to `webauthn.auth` route.

4. The POST response will be:
- a redirect response
- or a json response with a `callback` data.

Example:

```html


...


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;}
});
}
);

```

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:
```javascript
{
result: true,
callback: `webauthn.redirects.login` target url,
}
```

## Register a new key

To register a new webauthn key, the workflow is the following:
1. Open the `webauthn.register` page.
You can customize the register page view by calling `Webauthn::registerViewResponseUsing`. See [View response](#view-response)

The default behavior will open the [webauthn::register](/resources/views/register.blade.php) page.
You can also change the value of `webauthn.views.register` in the configuration file.

2. Or: Get the publicKey challenge by calling `webauthn.store.options` (if not provided).

3. Start the webauthn browser registration.
You can use the [`webauthn.js`](/resources/js/webauthn.js) library to do this.

Send the signed data to `webauthn.store` route.
The data should contain a `name` field with the webauthn key name.

4. The POST response will be:
- a redirect response
- or a json response with a `callback` data.

Example:

```html


...


var publicKey = {!! json_encode($publicKey) !!};

var webauthn = new WebAuthn();

webauthn.register(
publicKey,
function (data) {
axios.post("{{ route('webauthn.store') }}", {
...data,
name: "{{ $name }}",
})
}
);

```

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:
```javascript
{
result: json serialized webauthn key value,
callback: `webauthn.redirects.register` target url,
}
```

## Routes

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.

### Ignore route creation

You can disable the routes creation by adding this in your `AppServiceProvider`:

```php
use LaravelWebauthn\Services\Webauthn;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
Webauthn::ignoreRoutes();
}
}
```

## Customizing The Authentication Pipeline

The Laravel Webauthn authentication pipeline is highly inspired by the [Fortify pipeline](https://laravel.com/docs/9.x/fortify#customizing-the-authentication-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:

```php
use LaravelWebauthn\Actions\AttemptToAuthenticate;
use LaravelWebauthn\Actions\EnsureLoginIsNotThrottled;
use LaravelWebauthn\Actions\PrepareAuthenticatedSession;
use LaravelWebauthn\Services\Webauthn;
use Illuminate\Http\Request;

Webauthn::authenticateThrough(fn (Request $request) => array_filter([
config('webauthn.limiters.login') !== null ? null : EnsureLoginIsNotThrottled::class,
AttemptToAuthenticate::class,
PrepareAuthenticatedSession::class,
]));
```

## Rate Limiter

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](https://laravel.com/docs/11.x/routing#defining-rate-limiters) to create a new RateLimiter within the `boot` method of your application's `App\Providers\AppServiceProvider` class.

```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
protected function boot(): void
{
RateLimiter::for('webauthn-login', function (Request $request) {
return Limit::perMinute(1000);
});
}
}
```

Then use this new custom rate limiter in your `webauthn.limiters.login` configuration:

```php
'limiters' => [
'login' => 'webauthn-login',
],
```

## Events

Events are dispatched by LaravelWebauthn:

* `\LaravelWebauthn\Events\WebauthnLogin` on login with Webauthn check.
* `\LaravelWebauthn\Events\WebauthnLoginData` on preparing authentication data challenge.
* `\Illuminate\Auth\Events\Failed` on a failed login check.
* `\LaravelWebauthn\Events\WebauthnRegister` on registering a new key.
* `\LaravelWebauthn\Events\WebauthnRegisterData` on preparing register data challenge.
* `\LaravelWebauthn\Events\WebauthnRegisterFailed` on failing registering a new key.

## View response

You can easily change the view responses with the Webauthn service.

For instance, call `Webauthn::loginViewResponseUsing` in your `App\Providers\AppServiceProvider` class:

```php
use LaravelWebauthn\Services\Webauthn;

class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
Webauthn::loginViewResponseUsing(LoginViewResponse::class);
}
}
```

With a `LoginViewResponse` class:

```php
use LaravelWebauthn\Http\Responses\LoginViewResponse as LoginViewResponseBase;

class LoginViewResponse extends LoginViewResponseBase
{
public function toResponse($request)
{
return Inertia::render('Webauthn/WebauthnLogin', [
'publicKey' => $this->publicKey
])->toResponse($request);
}
}
```

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` |

# Compatibility

## Laravel compatibility

This package has the following Laravel compatibility:

| Laravel | [asbiin/laravel-webauthn](https://github.com/asbiin/laravel-webauthn) |
|----------|----------|
| 5.8-8.x | <= 1.2.0 |
| 7.x-8.x | 2.0.1 |
| >= 9.x | >= 3.0.0 |

## Browser compatibility

Most of the browsers [support Webauthn](https://caniuse.com/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)

### Homestead
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:

```yaml
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
```

# License

Author: [Alexis Saettler](https://github.com/asbiin)

Copyright © 2019–2024.

Licensed under the MIT License. [View license](/LICENSE.md).