goedemiddag/webauthn-fork

使用设备、指纹或生物识别数据验证用户。再见,密码!

4.2.6 2023-11-09 12:37 UTC

README

Lukenn Sabellano - Unsplash (UL) #RDufjtg6JpQ

Latest Stable Version License Coverage Status Laravel Octane Compatible

Larapass

使用设备、指纹或生物识别数据验证用户。再见,密码!

这使您可以在 Laravel 身份验证驱动程序内部启用 WebAuthn 身份验证,并包含 除了厨房用具之外的所有功能

要求

  • PHP 7.4 或 PHP 8.0
  • Laravel 7.18(2020 年 7 月)或 Laravel 8.x

对于 Laravel 9.x 及更高版本的支持,请使用 Laragear WebAuthn

安装

只需在控制台中使用 Composer 即可。

composer require darkghosthunter/larapass

不幸的是,使用 WebAuthn 并不是“轻而易举”,此包允许您以最 简单的方式 启用 WebAuthn。

目录

什么是 WebAuthn?它是如何使用指纹或其他方法的?

简而言之,主要浏览器与 Web 身份验证 API 兼容,将身份验证推向设备(指纹、面部 ID、图案、代码等),而不是纯文本密码。

此包使用自定义 用户提供程序 从设备验证 WebAuthn 有效负载。

如果您对 WebAuthn 有任何疑问,请查看此简短常见问题解答。对于更深入的了解,请查阅 WebAuthn.ioWebAuthn.meGoogle WebAuthn 教程

设置

我们需要确保您的用户可以注册其设备并使用它们进行验证。

  1. 添加 eloquent-webauthn 驱动程序.
  2. 创建 webauthn_credentials
  3. 实现合约和特质

之后,您可以使用包含的控制器和辅助程序快速开始 WebAuthn,使您的生活更加轻松。

  1. 注册路由
  2. 使用 JavaScript 辅助程序
  3. 设置账户恢复

1. 添加 eloquent-webauthn 驱动程序

此包包含一个与 Eloquent 兼容的 用户提供程序,用于验证来自设备的 WebAuthn 响应。

转到您的 config/auth.php 配置文件,并将您使用的提供程序的驱动程序更改为 eloquent-webauthn

return [
    // ...

    'providers' => [
        'users' => [
            // 'driver' => 'eloquent', // Default Eloquent User Provider 
            'driver' => 'eloquent-webauthn',
            'model' => App\User::class,
        ],
    ]
];

如果您计划为 WebAuthn 创建自己的用户提供程序驱动程序,请记住注入 WebAuthnAssertValidator 以正确验证用户和传入的响应。

2. 创建 webauthn_credentials

通过发布迁移文件并迁移表来创建 webauthn_credentials

php artisan vendor:publish --provider="DarkGhostHunter\Larapass\LarapassServiceProvider" --tag="migrations"
php artisan migrate

3. 实现合约和特质

WebAuthnAuthenticatable 合约和 WebAuthnAuthentication 特质添加到 Authenticatable 用户类,或任何使用认证的类。

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use DarkGhostHunter\Larapass\Contracts\WebAuthnAuthenticatable;
use DarkGhostHunter\Larapass\WebAuthnAuthentication;

class User extends Authenticatable implements WebAuthnAuthenticatable
{
    use WebAuthnAuthentication;

    // ...
}

该特质用于将用户模型与数据库中包含的 WebAuthn 数据关联起来。

4. 注册路由(可选)

最后,您需要添加注册和认证用户的路由。如果您想快速入门,只需发布 Larapass 中包含的控制器。

php artisan vendor:publish --provider="DarkGhostHunter\Larapass\LarapassServiceProvider" --tag="controllers"

您可以将这些路由定义复制粘贴到您的 routes/web.php 文件中。

use App\Http\Controllers\Auth\WebAuthnRegisterController;
use App\Http\Controllers\Auth\WebAuthnLoginController;

Route::post('webauthn/register/options', [WebAuthnRegisterController::class, 'options'])
     ->name('webauthn.register.options');
Route::post('webauthn/register', [WebAuthnRegisterController::class, 'register'])
     ->name('webauthn.register');

Route::post('webauthn/login/options', [WebAuthnLoginController::class, 'options'])
     ->name('webauthn.login.options');
Route::post('webauthn/login', [WebAuthnLoginController::class, 'login'])
     ->name('webauthn.login');

在您的前端脚本中,将这些请求指向这些路由。

如果您想完全控制,可以选择不使用这些辅助控制器,并使用自己的逻辑。如果您需要从某处开始,可以使用 AttestWebAuthnAssertsWebAuthn 特质。

5. 使用 JavaScript 辅助工具(可选)

此软件包包含一个方便的脚本,用于通过 WebAuthn 处理注册和登录。要使用它,只需将 larapass.js 资产发布到您的应用程序公共资源中。

php artisan vendor:publish --provider="DarkGhostHunter\Larapass\LarapassServiceProvider" --tag="public"

您将收到 vendor/larapass/js/larapass.js 文件,您可以将其包含到您的认证视图中,并按需使用它。

<script src="{{ asset('vendor/larapass/js/larapass.js') }}"></script>

<!-- Registering credentials -->
<script>
    const register = (event) => {
        event.preventDefault()
        new Larapass({
            register: 'webauthn/register',
            registerOptions: 'webauthn/register/options'
        }).register()
          .then(response => alert('Registration successful!'))
          .catch(response => alert('Something went wrong, try again!'))
    }

    document.getElementById('register-form').addEventListener('submit', register)
</script>

<!-- Login users -->
<script>
    const login = (event) => {
        event.preventDefault()
        new Larapass({
            login: 'webauthn/login',
            loginOptions: 'webauthn/login/options'
        }).login({
            email: document.getElementById('email').value
        }).then(response => alert('Authentication successful!'))
          .catch(error => alert('Something went wrong, try again!'))
    }

    document.getElementById('login-form').addEventListener('submit', login)
</script>

如果您使用默认值,可以省略路由列表声明。上面的示例只是为了展示。请确保根据您的需求修改此脚本。

此外,辅助工具允许在注册和登录的动作请求中添加头信息。

new Larapass({
    login: 'webauthn/login',
    loginOptions: 'webauthn/login/options'
}).login({
    email: document.getElementById('email').value,
}, {
    'My-Custom-Header': 'This is sent with the signed challenge',
})

您可以将它复制粘贴并导入到 Laravel MixBabelWebpack 这样的转译器中。如果脚本不符合您的需求,您也可以创建自己的脚本。

记住用户

您可以通过在从前端推送签名的登录挑战时将 WebAuthn-Remember 头值设置为 true 来启用它。我们可以使用 包含的 JavaScript 辅助工具 轻松完成此操作。

new Larapass.login({
    email: document.getElementById('email').value
}, {
    'WebAuthn-Remember': true
})

或者,如果您使用自己的脚本,可以在出发的 JSON 负载数组中添加 remember 键。两种方式都可行。

您可以在 AssertsWebAuthn 特质中覆盖此行为。

6. 设置账户恢复(可选)

您可能想提供一个“恢复”账户的方法,以防用户丢失其凭据,这基本上是附加一个新的凭据。您可以使用这些控制器,这些控制器也已发布,以及这些路由

use App\Http\Controllers\Auth\WebAuthnDeviceLostController;
use App\Http\Controllers\Auth\WebAuthnRecoveryController;

Route::get('webauthn/lost', [WebAuthnDeviceLostController::class, 'showDeviceLostForm'])
     ->name('webauthn.lost.form');
Route::post('webauthn/lost', [WebAuthnDeviceLostController::class, 'sendRecoveryEmail'])
     ->name('webauthn.lost.send');

Route::get('webauthn/recover', [WebAuthnRecoveryController::class, 'showResetForm'])
     ->name('webauthn.recover.form');
Route::post('webauthn/recover/options', [WebAuthnRecoveryController::class, 'options'])
     ->name('webauthn.recover.options');
Route::post('webauthn/recover/register', [WebAuthnRecoveryController::class, 'recover'])
     ->name('webauthn.recover');

这些控制器附带 新视图翻译行,因此如果您对包含的内容不满意,可以覆盖它们。

您还可以在 resources/vendor/larapass 中覆盖视图,并使用用户的 sendCredentialRecoveryNotification 方法发送通知。

之后,不要忘记在您的 config/auth.php 中添加一个新的令牌经纪人。我们需要它来存储恢复过程中的令牌。

return [
    // ...

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],

        // New for WebAuthn
        'webauthn' => [
            'provider' => 'users', // The user provider using WebAuthn.
            'table' => 'web_authn_recoveries', // The table to store the recoveries.
            'expire' => 60,
            'throttle' => 60,
        ],
    ],
];

确认中间件

遵循与 password.confirm 中间件 相同的原则,Larapass 包含了一个 webauthn.confirm 中间件,该中间件会在用户进入特定路由之前,要求用户使用其设备进行确认。

Route::get('this/is/important', function () {
    return 'This is very important!';
})->middleware('webauthn.confirm');

发布控制器 时,WebAuthnConfirmController 将会在你的控制器文件中就绪,等待接收确认。你只需要通过复制粘贴以下内容来注册路由。

Route::get('webauthn/confirm', 'Auth\WebAuthnConfirmController@showConfirmForm')
     ->name('webauthn.confirm.form');
Route::post('webauthn/confirm/options', 'Auth\WebAuthnConfirmController@options')
     ->name('webauthn.confirm.options');
Route::post('webauthn/confirm', 'Auth\WebAuthnConfirmController@confirm')
     ->name('webauthn.confirm');

与以往一样,你可以根据自己的逻辑选择退出。对于这种情况,请查看 ConfirmsWebAuthn 特性 以开始。

你可以在配置中更改保留确认的时间

事件

由于所有认证都由 Laravel 本身处理,因此包含的唯一事件是 事件,即 AttestationSuccessful,它在注册成功时触发。它包含持久化的凭据用户。

你可以使用此事件来执行操作,例如通知用户已注册新设备。为此,你可以使用一个 监听器

public function handle(AttestationSuccessful $event)
{
    $event->user->notify(
        new DeviceRegisteredNotification($event->credential->getId())
    );
}

使用 WebAuthn 的操作

此包简化了操作 WebAuthn 的 仪式(认证和断言)。为此,请使用方便的 WebAuthn 门面。

认证(注册)

使用 generateAttestationvalidateAttestation 为你的用户进行操作。后者返回验证过的凭据,因此你可以手动保存它们。

<?php

use App\User; 
use Illuminate\Support\Facades\Auth;
use DarkGhostHunter\Larapass\Facades\WebAuthn;

$user = Auth::user();

// Create an attestation for a given user.
return WebAuthn::generateAttestation($user);

然后我们可以在以后验证它

<?php

use App\User; 
use Illuminate\Support\Facades\Auth;
use DarkGhostHunter\Larapass\Facades\WebAuthn;

$user = Auth::user();

// Verify it
$credential = WebAuthn::validateAttestation(
    request()->json()->all(), $user
);

// And save it.
if ($credential) {
    $user->addCredential($credential);
} else {
    return 'Something went wrong with your device!';
}

断言(登录)

对于断言,只需使用 generateAssertion 创建请求,并使用 validateAssertion 进行验证。

<?php

use App\User; 
use DarkGhostHunter\Larapass\Facades\WebAuthn;

// Find the user to assert, if there is any
$user = User::where('email', request()->input('email'))->first();

// Create an assertion for the given user (or a blank one if not found);
return WebAuthn::generateAssertion($user);

然后我们可以在以后验证它

<?php

use App\User;
use Illuminate\Support\Facades\Auth;
use DarkGhostHunter\Larapass\Facades\WebAuthn;

// Verify the incoming assertion.
$credentials = WebAuthn::validateAssertion(
    request()->json()->all()
);

// If is valid, login the user of the credentials.
if ($credentials) {
    Auth::login(
        User::getFromCredentialId($credentials->getPublicKeyCredentialId())
    );
}

凭据

你可以通过用户实例直接管理用户凭据,这得益于 WebAuthnAuthenticatable 接口。最有用的方法是

  • hasCredential():检查用户是否有特定的凭据 ID。
  • addCredential():添加新的凭据来源。
  • removeCredential():通过其 ID 移除现有的凭据。
  • flushCredentials():删除所有凭据。你可以通过其 ID 排除凭据。
  • enableCredential():将现有的凭据 ID 包含在认证中。
  • disableCredential():从认证中排除现有的凭据 ID。
  • getFromCredentialId():如果存在,则返回使用给定凭据 ID 的用户。

你可以使用这些方法,例如,将被盗设备/凭据列入黑名单并注册新的,或者通过清除所有已注册设备来完全禁用 WebAuthn。

高级配置

Larapass 旨在即插即用,但你可以通过发布配置文件来覆盖配置。

php artisan vendor:publish --provider="DarkGhostHunter\Larapass\LarapassServiceProvider" --tag="config"

之后,你将收到包含此数组的 config/larapass.php 配置文件

<?php

return [
    'relaying_party' => [
        'name' => env('WEBAUTHN_NAME', env('APP_NAME')),
        'id'   => env('WEBAUTHN_ID'),
        'icon' => env('WEBAUTHN_ICON'),
    ],
    'bytes' => 16,
    'timeout' => 60,
    'cache' => env('WEBAUTHN_CACHE'),
    'algorithms' => [
        \Cose\Algorithm\Signature\ECDSA\ES256::class,
        \Cose\Algorithm\Signature\EdDSA\Ed25519::class,
        \Cose\Algorithm\Signature\ECDSA\ES384::class,
        \Cose\Algorithm\Signature\ECDSA\ES512::class,
        \Cose\Algorithm\Signature\RSA\RS256::class,
    ],
    'attachment' => null,
    'conveyance' => 'none',
    'login_verify' => 'preferred',
    'userless' => null,
    'unique' => false,
    'fallback' => true,
    'confirm_timeout' => 10800,
];

转接方信息

return [
    'relaying_party' => [
        'name' => env('WEBAUTHN_NAME', env('APP_NAME')),
        'id'   => env('WEBAUTHN_ID'),
        'icon' => env('WEBAUTHN_ICON'),
    ],
];

转接方 只是一种在用户设备中唯一标识你的应用的方式

  • name:应用的名称。默认为应用名称。
  • id:应用的可选域名。如果为 null,设备将内部填充。
  • icon:可选的 BASE64(最大 128 字节)图像数据或图像 URL。

考虑使用基础域名如 myapp.com 作为 id,以允许所有子域名(如 foo.myapp.com)上的凭据。

挑战配置

return [
    'bytes' => 16,
    'timeout' => 60,
    'cache' => env('WEBAUTHN_CACHE'),
];

待签名的出站挑战是一个随机字节数组。这控制了字节数量、挑战的超时时间(之后被标记为无效),以及设备在解决挑战时使用的缓存。

算法

return [
    'algorithms' => [
        \Cose\Algorithm\Signature\ECDSA\ES256::class,   // ECDSA with SHA-256
        \Cose\Algorithm\Signature\EdDSA\Ed25519::class, // EdDSA
        \Cose\Algorithm\Signature\ECDSA\ES384::class,   // ECDSA with SHA-384
        \Cose\Algorithm\Signature\ECDSA\ES512::class,   // ECDSA with SHA-512
        \Cose\Algorithm\Signature\RSA\RS256::class,     // RSASSA-PKCS1-v1_5 with SHA-256
    ],
];

这控制了认证器(设备)如何操作来创建公钥和私钥。这些COSE算法是最适用于设备内和漫游密钥的,因为某些算法必须在低带宽协议上传输。

除非你知道自己在做什么,否则请添加或删除类。真的。只需保持它们原样。

密钥附加

return [
     'attachment' => null,
];

默认情况下,用户决定使用什么进行注册。如果您希望仅使用跨平台认证(如USB密钥、CA服务器或证书),请将其设置为true,或者如果您想强制执行仅设备认证,请设置为false

证言传递

return [
    'conveyance' => null,
];

认证传递表示您是否需要验证设备密钥。虽然大多数时候不需要,但您可以将此更改为indirect(您验证它来自可信来源)或direct(设备包含验证数据)。

如果您不知道自己在做什么,请保持原样。

登录验证

return [
    'login_verify' => 'preferred',
];

默认情况下,大多数认证器在登录时会要求用户验证。您可以覆盖此设置并将其设置为required,如果您想没有例外。

您还可以使用discouraged来仅检查用户存在(如“继续”按钮),这可能使登录更快,但会稍微降低安全性。

当将无用户设置为preferredrequired时,将自动覆盖为required

无用户登录(一键,无类型)

return [
    'userless' => null,
];

您可以在设备注册时激活无用户登录,也称为一键登录或无键盘登录。在这种情况下,应将其更改为preferred,因为并非所有设备都支持此功能。

如果此功能被激活(不是nulldiscouraged),则登录验证将是强制性的。

这不会影响登录过程,只会影响认证(注册)。

唯一

return [
    'unique' => false,
];

如果为真,则设备将仅通过设备创建一个凭证。这是通过告诉设备用户已有的凭证ID列表来实现的。如果在设备中已存在至少一个,则后者将返回错误。

密码回退

return [
    'fallback' => true,
];

默认情况下,此包允许在凭证不是WebAuthn JSON有效负载时,使用相同的eloquent-webauthn驱动程序使用密码登录用户。

禁用回退选项将仅验证WebAuthn凭证。要处理经典用户/密码场景,您可能需要创建一个单独的守卫。

确认超时

return [
    'confirm_timeout' => 10800,
];

当使用确认中间件时,确认将在设定的秒数内被记住。默认为3小时,这对于大多数场景来说已经足够。

证言和元数据语句支持

如果您需要非常高的安全性,您应使用认证和元数据声明。您基本上会要求认证器证明其真实性,并以多种方式对其进行检查。

为此,请查看此文章并根据需要扩展服务容器中的类。

<?php

use Webauthn\AttestationStatement\AttestationStatementSupport;
use Webauthn\AttestationStatement\AndroidSafetyNetAttestationStatementSupport;

$this->app->extend(AttestationStatementSupport::class, function ($manager) {
    $manager->add(new AndroidSafetyNetAttestationStatementSupport());
});

安全

这是关于此WebAuthn实现的一些详细信息。

  • 注册(认证)专属于域名、IP和用户。
  • 登录(断言)如果指定,则专属于域名、IP和用户。
  • 缓存的挑战在解决后总是会忘记,无论结果如何。
  • 缓存的挑战TTL与WebAuthn超时相同(默认60秒)。
  • 包含的控制器包括对WebAuthn端点的节流。
  • 用户ID(句柄)是随机UUID v4。
  • 凭证可以被列入黑名单(启用/禁用)。

如果您发现任何安全问题,请通过电子邮件发送至 darkghosthunter@gmail.com 而不是使用问题跟踪器。

顺便说一句,如果您所在的应用程序位于负载均衡器后面,请记得 正确配置应用程序

常见问题解答

  • 这个功能是否与任何浏览器兼容?

是的。对于旧浏览器,您应该有一个后备检测脚本。这可以通过 包含的JavaScript辅助器 轻松实现。

if (! Larapass.supportsWebAuthn()) {
   alert('Your device is not secure enough to use this site!');
}
  • 这会将用户的指纹、PIN码或图案存储在我的网站上吗?

不。它存储由设备生成的公钥。

  • 钓鱼网站能否窃取WebAuthn凭据并在我的网站上使用它们?

不。WebAuthn终结了钓鱼。

  • WebAuthn数据能否识别特定设备?

不,除非明确请求并获得同意。

  • 我的用户的传统密码安全吗?

是的,只要您像应该那样对它们进行散列,并且您已经保护了您的应用程序密钥。Laravel默认会这样做。您还可以 禁用它们

  • 用户能否注册两个或更多的 设备

是的。

  • 如果凭据被克隆会发生什么?

由于“登录”计数器将大于原始设备报告的计数,用户将无法通过身份验证。要干预此过程,请修改服务容器中的Assertion Validator并添加自己的 CounterChecker

$this->app->bind(CounterChecker::class, function () {
    return new \App\WebAuthn\MyCountChecker;
});

在您的计数器检查器中,您可能希望在计数器低于报告的值时抛出异常。

<?php

namespace App\WebAuthn;

use Webauthn\Counter\CounterChecker;
use App\Exceptions\WebAuthn\CredentialCloned;
use Webauthn\PublicKeyCredentialSource as Credentials;

class MyCountChecker implements CounterChecker
{
    public function check(Credentials $credentials, int $currentCounter) : void
    {
        if ($credentials->getCounter() <= $currentCounter) {
            throw new CredentialCloned($credentials);
        } 
    }
}
  • 如果用户丢失了他的设备,他能否注册新设备?

是的,使用这些恢复辅助器

  • 禁用和删除凭据之间的区别是什么?

禁用凭据不会删除它,因此可以在用户恢复它的情况下稍后手动启用。当凭据被删除时,它将永远消失。

  • 用户能否从其设备中删除其凭据?

是的。如果这样做,服务器上凭据的其余部分将几乎被孤儿化。您可能希望向用户显示注册凭据列表,以便删除它们。

  • 这对密码或双因素认证有多安全?

极其安全,因为它仅在HTTPS(或localhost)上工作,且没有密码交换,也没有代码在屏幕上可见。

  • 我能禁用密码回退吗?我能强制仅使用WebAuthn身份验证吗?

是的。只需确保 使用恢复辅助器 以避免锁定用户。

  • 这包含前端JavaScript吗?

是的,但它非常 基本

  • 这会自动在前端编码/解码字符串吗?

是的,包含的 WebAuthn Helper 会为您自动完成。

  • 这包括凭据恢复路由吗?

是的。

  • 我能否通过PC桌面/笔记本电脑/终端将我的智能手机用作认证器?

这取决于操作系统和硬件。一些将需要先将设备配对到“帐户”。其他一些将仅与USB密钥一起工作。这取决于硬件和软件供应商。

  • 为什么我的设备没有显示Windows Hello/TouchId/FaceId/指纹认证?

默认情况下,这个WebAuthn实现几乎接受一切。设备、操作系统和Web浏览器的某些组合可能在可用的WebAuthn身份验证方面有所不同。换句话说,这不是我的错。

  • 我正在尝试在我的开发服务器上测试这个,但它不起作用

请仅使用 localhost,或者使用 ngrok(或类似工具)通过 HTTPS 隧道连接您的网站。WebAuthn 仅在 localhost 或仅通过 HTTPS 下工作。

许可

本软件遵循 MIT 许可协议。有关更多信息,请参阅 许可文件

Laravel 是 Taylor Otwell 的商标。版权所有 © 2011-2020 Laravel LLC。