jbtronics/2fa-webauthn

为 scheb/2fa 添加 Webauthn 二次认证插件

安装次数: 13,806

依赖项: 0

建议者: 0

安全: 0

星标: 10

关注者: 3

分支: 1

开放问题: 0

类型:symfony-bundle

v2.2.2 2024-07-21 19:24 UTC

This package is auto-updated.

Last update: 2024-09-21 19:50:26 UTC


README

此仓库包含一个插件,为 scheb/2fa 添加对 Webauthn 验证器(如 Yubikey)作为第二因素的支持。

功能

  • 支持所有 Webauthn 验证器作为第二因素
  • 支持每个用户多个验证器
  • 对现有已注册的 U2F 密钥(来自 r/u2f-two-factor-bundle)的向后兼容性

要求

  • Symfony 6
  • PHP 8.1 或更高版本
  • webauthn/webauthn-lib 4.0 或更高版本

如果您想使用 symfony 5.* 和 PHP 7.4,请使用此捆绑包的 1.0.0 版本。

安装

  1. 安装捆绑包 composer require jbtronics/2fa-webauthn
  2. 在您的 config/bundles.php 中启用捆绑包(通常由 Symfony flex 自动完成)
  3. 如果您想使用 symfony 5.* 和 PHP 7.4,使用此捆绑包的 1.0.0 版本。您不需要运行社区食谱,因为我们只使用捆绑包中的 doctrine 类型定义。将 Webauthn\Bundle\WebauthnBundle::class => ['all' => true], 添加到您的 config/bundles.php 中。

设置和使用

在遵循安装步骤后,请执行以下步骤来设置库

  1. Jbtronics\TFAWebauthn\Model\TwoFactorInterface 接口添加到您的用户实体
use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface;

class User implements WebauthnTwoFactorInterface
{
    /** 
     * @var Collection<int, PublicKeyCredentialSource>
     * @ORM\OneToMany(targetEntity="App\Entity\WebauthnKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true)
     */
    private $webauthnKeys;
    
    /**
     * Determines whether the user has 2FA using Webauthn enabled
     * @return bool True if the webauthn 2FA is enabled, false otherwise
     */
    public function isWebAuthnAuthenticatorEnabled(): bool
    {
        //Return true to enable webauthn 2FA
        return count($this->webauthnKeys) > 0;
    }
    
    /**
     * Returns a list of all legacy U2F keys, associated with this user
     * Return an empty array, if this user does not have any legacy U2F keys.
     * @return iterable<LegacyU2FKeyInterface>
     */
    public function getLegacyU2FKeys(): iterable
    {
        return []; //If you have no legacy U2F keys, return just an empty array
        //return $this->u2f_keys; //Otherwise return the legacy keys (see migration section below)
    }

    /**
     * Returns a list of all webauthn keys, associated with this user
     * @return iterable<PublicKeyCredentialSource>
     */
    public function getWebauthnKeys(): iterable
    {
        return $this->webauthnKeys;
    }

    /**
     * Returns the webauthn user entity that should be used for this user.
     * @return PublicKeyCredentialUserEntity
     */
    public function getWebAuthnUser(): PublicKeyCredentialUserEntity
    {
        //Return webauthn user definition for this user. As we just use it as an two-factor authentication, the values here are most likely not that important
        return new PublicKeyCredentialUserEntity(
            $this->getUsername(), // The Webauthn Name (like a username)
            $this->getID(), // A unique identifier for this user
            $this->getDisplayName() // The display name of this user (optional, otherwise null)
        );
    }
}
  1. 为 webauthn 密钥创建一个新的实体。为了简单起见,我们使用 web-auth/webauthn-symfony-bundle 的模板(有关更多信息,请参阅 此处
declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Webauthn\PublicKeyCredentialSource as BasePublicKeyCredentialSource;
use Webauthn\TrustPath\TrustPath;

/**
 * @ORM\Table(name="webauthn_keys")
 * @ORM\Entity()
 */
class WebAuthnKey extends BasePublicKeyCredentialSource
{
    /**
     * @var string
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
    
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="webauthnKeys")
     **/
    protected ?User $user = null;
    
    //You can declare additional fields too, if you want to store additional information about the key (like a name)
    private $name;


    public function getId(): string
    {
        return $this->id;
    }
    
    public static function fromRegistration(BasePublicKeyCredentialSource $registration): self
    {
        return new static(
            $registration->getPublicKeyCredentialId(),
            $registration->getType(),
            $registration->getTransports(),
            $registration->getAttestationType(),
            $registration->getTrustPath(),
            $registration->getAaguid(),
            $registration->getCredentialPublicKey(),
            $registration->getUserHandle(),
            $registration->getCounter(),
            $registration->getOtherUI()
        );
    }
}
  1. 将前端 JavaScript 代码包含到您的项目中:对于 webauthn,我们需要一些 JavaScript 代码来与验证器交互。将文件从 src/Resources/assets/tfa_webauthn.js 复制到您的项目中,并通过 <script> 标签加载或通过在 webpack 中使用 .addEntry() 包含它。

  2. 添加配置文件 config/packages/jbtronics_2fa_webauthn.yaml

tfa_webauthn:
  enabled: true
  
  # Optional configuration options:

  # timeout: 60000 # The timeout in millisceconds to allow the user to interact with the authenticator. Default: 60000
  # template: '' # The template to use for the login form
  
  # rpID: null # The relying party ID of your application. If null, the current host will be used. Default: null
  # U2FAppID: null # The U2F AppID of your application. If null, the current host will be used. Default: null
  
  # These settings are most likely not important for two-factor authentication:
  # rpName: 'My Application' # The relying party name of your application, Default: 'My Application'
  # rpIcon: null # The relying party icon of your application. Default: null
  1. 自定义登录模板:类似于 scheb/2fa 捆绑包的基础登录模板,您可能需要覆盖此捆绑包的登录模板以将其集成到设计中。将模板从 Resources/views/Authentication/form.html.twig 复制到您的项目中,并根据您的需求进行自定义。在捆绑包配置中配置 template 设置为您的新的路径。

新密钥的注册

原则上,使用现有密钥的登录应该可以工作,但您可能需要一些注册新密钥的可能性。为了简化这个过程,有一个 Jbtronics\TFAWebauthn\Services\TFAWebauthnRegistrationHelper 服务可以帮助您完成此操作

  1. 创建一个新的控制器,用于处理注册,其外观应如下所示
    use Jbtronics\TFAWebauthn\Services\TFAWebauthnRegistrationHelperclass WebauthnKeyRegistrationController extends AbstractController
{
    /**
     * @Route("/webauthn/register", name="webauthn_register")
     */
    public function register(Request $request, TFAWebauthnRegistrationHelper $registrationHelper, EntityManagerInterface $em)
    {

        //If form was submitted, check the auth response
        if ($request->getMethod() === 'POST') {
            $webauthnResponse = $request->request->get('_auth_code');

            //Retrieve other data from the form, that you want to store with the key
            $keyName = $request->request->get('keyName');


            try {
                //Check the response
                $new_key = $registrationHelper->checkRegistrationResponse($webauthnResponse);
            } catch (Exception $exception) {
                // Handle errors...
            }
            
            //If we got here, the registration was successful. Now we can store the new key in the database
            
            //Convert our returned key into an database entity and persist it...
            $keyEntity = WebauthnKey::fromRegistration($new_key);
            $keyEntity->setName($keyName);
            $keyEntity->setUser($this->getUser());

            $em->persist($keyEntity);
            $em->flush();
            
            
            $this->addFlash('success', 'Key registered successfully');
            //We are finished here so return to another page
            return $this->redirectToRoute('homepage');
        }


        return $this->render(
            'webauthn_register.html.twig',
            [
                //Generate the registration request
                'registrationRequest' => $registrationHelper->generateRegistrationRequestAsJSON(),
            ]
        );
    }
}
  1. 创建一个包含表单的模板,该表单将用于注册新密钥。表单应如下所示
<form method="post" class="form" action="{{ path('webauthn_register') }}" data-webauthn-tfa-action="register" data-webauthn-tfa-data='{{ registrationRequest|raw }}'>
    <input type="text" name="keyName" id="keyName" placeholder="Shown key name"/>
                
    <button type="submit" class="btn btn-success">Add new Key</button>
        
    <input type="hidden" name="_auth_code" id="_auth_code" />
        
</form>

data-webauthn-tfa-action 属性将表单标记为 webauthn 注册表单,并由上述包含的前端代码处理。如果表单被提交,前端代码将捕获该操作并启动注册过程。响应将被放入具有 id _auth_code 的隐藏输入字段中,并发送到我们的控制器进行解析。

从 r/u2f-two-factor-bundle 迁移

  1. 将您的 U2FKey 实体的 R\U2FTwoFactorBundle\Model\U2F\TwoFactorKeyInterface 接口替换为 Jbtronics\TFAWebauthn\Model\LegacyU2FKeyInterface,并删除 fromRegistrationData() 函数(因为我们不再需要它)。
  2. 将您的用户中 R\U2FTwoFactorBundle\Model\U2F\TwoFactorInterface 接口替换为 Jbtronics\TFAWebauthn\Model\TwoFactorInterface,配置它(见上文)并将您的 getU2FKeys() 函数重命名为 getLegacyU2FKeys()
  3. (可选:)如果您的应用ID与域名不同,请使用 U2FAppID 选项进行配置。但通常情况下,这通常不需要。
  4. 删除 r/u2f-two-factor-bundle 的旧路由、模板和设置,并将其从您的应用程序中移除
  5. 按照上述设置步骤进行

许可证

本软件包受 MIT 许可证的许可。有关详细信息,请参阅 LICENSE

致谢