darkghosthunter / larapass
使用设备、指纹或生物识别数据验证用户。再见了,密码!
Requires
- php: >=7.4.0
- ext-json: *
- illuminate/support: ^8.0
- nyholm/psr7: ^1.3
- ramsey/uuid: ^4.0
- symfony/psr-http-message-bridge: ^2.0
- thecodingmachine/safe: ^1.3.3
- web-auth/webauthn-lib: ^3.3
Requires (Dev)
- laravel/framework: 8.*
- orchestra/testbench: ^6.7.2
- phpunit/phpunit: ^9.5.2
This package is auto-updated.
Last update: 2022-07-12 19:49:10 UTC
README
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,将认证推向设备(指纹、面部识别、图案、代码等),而不是明文密码。
此软件包使用自定义 用户提供者 来验证来自设备的 WebAuthn 有效负载。
如果您对 WebAuthn 有任何疑问,请查看此小型常见问题解答。要深入了解,请访问 WebAuthn.io、WebAuthn.me 和 Google WebAuthn 教程。
设置
我们需要确保您的用户可以注册他们的设备并使用它们进行认证。
之后,您可以使用包含的控制器和辅助工具快速开始 WebAuthn,以简化您的工作。
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');
在您的前端脚本中,将请求指向这些路由。
如果您想要完全控制,可以选择不使用这些辅助控制器并使用您自己的逻辑。如果您需要从头开始,可以使用
AttestWebAuthn
和AssertsWebAuthn
特质。
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 Mix、Babel或Webpack之类的转译器中。如果脚本不符合您的需求,您可以自由创建自己的。
记住用户
您可以通过在将签名登录挑战从前端推送时将WebAuthn-Remember
标题值设置为true
来启用它。我们可以使用包含的JavaScript助手轻松做到这一点。
new Larapass.login({ email: document.getElementById('email').value }, { 'WebAuthn-Remember': true })
或者,如果您使用自己的脚本,可以将remember
键添加到输出的JSON有效负载中。两种方式都受接受。
您可以在
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');
这些包括新视图和翻译行,因此如果您对包含的内容不满意,可以覆盖它们。
您还可以通过用户sendCredentialRecoveryNotification
方法的resources/vendor/larapass
中的视图和发送的通知来覆盖视图。
之后,不要忘记在您的config/auth.php
中添加一个新的token代理。我们将需要它来存储恢复过程中的令牌。
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
外观。
认证(注册)
使用generateAttestation
和validateAttestation
为您的用户。后者返回已验证的凭据,因此您可以手动保存它们。
<?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
来仅检查用户是否存在(如“继续”按钮),这可能会使登录更快,但安全性略低。
当将无用户设置为
preferred
或required
时,将自动将其重置为required
。
无用户登录(一键,无输入)
return [ 'userless' => null, ];
您可以在设备注册时激活无用户登录,也称为一键登录或无输入登录。在这种情况下,应将其更改为preferred
,因为并非所有设备都支持该功能。
如果已激活(不是null
或discouraged
),则登录验证将是强制性的。
这不会影响登录流程,只会影响证明(注册)。
唯一
return [ 'unique' => false, ];
如果为true,设备将限制设备只能创建一个凭据。这是通过告诉设备用户已经拥有的凭据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完成。你也可以禁用它。
- 用户能否注册两个或更多设备?
是的。
- 如果凭据被克隆会发生什么?
由于“登录”计数器将大于原始设备报告的,用户将无法通过身份验证。要干预该过程,请在服务容器中修改断言验证器并添加自己的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); } } }
- 如果用户丢失了他的设备,他能注册新设备吗?
是的,使用这些恢复辅助工具。
- 禁用和删除凭据之间的区别是什么?
禁用凭据不会删除它,因此如果用户恢复它,可以稍后手动启用。当凭据被删除时,它将永远消失。
- 用户能否从其设备中删除其凭据?
是的。如果这样做,服务器上凭据的其他部分将被虚拟地遗弃。你可能想向用户显示注册凭据的列表,以便它们可以删除。
- 这是对密码或2FA有多安全?
极其安全,因为它仅在HTTPS(或localhost
)上工作,并且没有密码交换,或屏幕上可见的代码。
- 我能禁用密码回退吗?我能强制只进行WebAuthn身份验证吗?
是的。只需确保使用恢复辅助工具,以免将用户锁定在外。
- 这包括前端JavaScript吗?
是的,但它非常基本。
- 这会在前端自动编码/解码字符串吗?
是的,包含的WebAuthn辅助工具会自动为您完成。
- 这包括凭据恢复路由吗?
- 我能否通过PC桌面/笔记本电脑/终端使用我的智能手机作为认证器?
这取决于操作系统和硬件。有些可能需要在“帐户”中预先配对设备。其他一些可能仅适用于USB密钥。这取决于硬件和软件供应商。
- 为什么我的设备不显示Windows Hello/TouchId/FaceId/指纹认证?
默认情况下,此WebAuthn实现几乎接受任何东西。某些设备、操作系统和Web浏览器的组合可能不同,不知道为WebAuthn身份验证提供什么。换句话说,这不是我的错。
- 我正在尝试在我的开发服务器上测试这个,但它不起作用
请仅使用localhost
,或使用ngrok(或类似)通过HTTPS隧道您的网站。WebAuthn仅在localhost
或HTTPS上工作。
许可证
MIT许可证(MIT)。有关更多信息,请参阅许可证文件。
Laravel是Taylor Otwell的商标。版权所有©2011-2020 Laravel LLC。