Use jbtronics/2fa-webauthn for u2f two factor authentication

This commit is contained in:
Jan Böhmer 2022-10-03 23:09:50 +02:00
parent 03aaff3c79
commit 068daeda75
18 changed files with 1389 additions and 604 deletions

View file

@ -0,0 +1,41 @@
<?php
namespace App\Controller;
use Jbtronics\TFAWebauthn\Services\TFAWebauthnRegistrationHelper;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class WebauthnKeyRegistrationController extends AbstractController
{
/**
* @Route("/webauthn/register", name="webauthn_register")
*/
public function register(Request $request, TFAWebauthnRegistrationHelper $registrationHelper)
{
//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');
//Check the response
$new_key = $registrationHelper->checkRegistrationResponse($webauthnResponse);
dump($new_key);
$this->addFlash('success', 'Key registered successfully');
}
return $this->render(
'Security/U2F/u2f_register.html.twig',
[
'registrationRequest' => $registrationHelper->generateRegistrationRequestAsJSON(),
]
);
}
}

View file

@ -44,8 +44,7 @@ namespace App\Entity\UserSystem;
use App\Entity\Base\TimestampTrait;
use Doctrine\ORM\Mapping as ORM;
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorKeyInterface;
use u2flib_server\Registration;
use Jbtronics\TFAWebauthn\Model\LegacyU2FKeyInterface;
/**
* @ORM\Entity
@ -56,7 +55,7 @@ use u2flib_server\Registration;
* })
* @ORM\HasLifecycleCallbacks()
*/
class U2FKey implements TwoFactorKeyInterface
class U2FKey implements LegacyU2FKeyInterface
{
use TimestampTrait;
@ -110,14 +109,6 @@ class U2FKey implements TwoFactorKeyInterface
**/
protected ?User $user = null;
public function fromRegistrationData(Registration $data): void
{
$this->keyHandle = $data->keyHandle;
$this->publicKey = $data->publicKey;
$this->certificate = $data->certificate;
$this->counter = $data->counter;
}
public function getKeyHandle(): string
{
return $this->keyHandle;

View file

@ -58,6 +58,7 @@ use App\Security\Interfaces\HasPermissionsInterface;
use App\Validator\Constraints\Selectable;
use App\Validator\Constraints\ValidPermission;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Webauthn\PublicKeyCredentialUserEntity;
use function count;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
@ -65,8 +66,6 @@ use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Exception;
use function in_array;
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorInterface as U2FTwoFactorInterface;
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorKeyInterface;
use Scheb\TwoFactorBundle\Model\BackupCodeInterface;
use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface;
use Scheb\TwoFactorBundle\Model\PreferredProviderInterface;
@ -74,6 +73,7 @@ use Scheb\TwoFactorBundle\Model\TrustedDeviceInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface;
/**
* This entity represents a user, which can log in and have permissions.
@ -86,7 +86,7 @@ use Symfony\Component\Validator\Constraints as Assert;
* @ORM\EntityListeners({"App\EntityListeners\TreeCacheInvalidationListener"})
* @UniqueEntity("name", message="validator.user.username_already_used")
*/
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, U2FTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, WebauthnTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface
{
//use MasterAttachmentTrait;
@ -838,48 +838,38 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
++$this->trustedDeviceCookieVersion;
}
/**
* Check if U2F is enabled.
*/
public function isU2FAuthEnabled(): bool
{
return count($this->u2fKeys) > 0;
}
/**
* Get all U2F Keys that are associated with this user.
*
* @psalm-return Collection<int, TwoFactorKeyInterface>
*/
public function getU2FKeys(): Collection
{
return $this->u2fKeys;
}
/**
* Add a U2F key to this user.
*/
public function addU2FKey(TwoFactorKeyInterface $key): void
{
$this->u2fKeys->add($key);
}
/**
* Remove a U2F key from this user.
*/
public function removeU2FKey(TwoFactorKeyInterface $key): void
{
$this->u2fKeys->removeElement($key);
}
public function getPreferredTwoFactorProvider(): ?string
{
//If U2F is available then prefer it
if ($this->isU2FAuthEnabled()) {
return 'u2f_two_factor';
}
//if ($this->isU2FAuthEnabled()) {
// return 'u2f_two_factor';
//}
//Otherwise use other methods
return null;
}
public function isWebAuthnAuthenticatorEnabled(): bool
{
return count($this->u2fKeys) > 0;
}
public function getLegacyU2FKeys(): iterable
{
return $this->u2fKeys;
}
public function getWebAuthnUser(): PublicKeyCredentialUserEntity
{
return new PublicKeyCredentialUserEntity(
$this->getUsername(),
(string) $this->getId(),
$this->getFullName(),
);
}
public function getWebauthnKeys(): iterable
{
return [];
}
}

View file

@ -146,7 +146,7 @@ final class PasswordChangeNeededSubscriber implements EventSubscriberInterface
*/
public static function TFARedirectNeeded(User $user): bool
{
$tfa_enabled = $user->isU2FAuthEnabled() || $user->isGoogleAuthenticatorEnabled();
$tfa_enabled = $user->isWebAuthnAuthenticatorEnabled() || $user->isGoogleAuthenticatorEnabled();
return null !== $user->getGroup() && $user->getGroup()->isEnforce2FA() && !$tfa_enabled;
}