Webauthn Framework
v3.0
v3.0
  • Introduction
  • Web Browser Support
  • Installation
  • Contributing
  • Webauthn In A Nutshell
    • Authenticators
    • Ceremonies
  • Pre-requisites
    • The Relying Party
    • Credential Source Repository
    • User Entity
    • Javascript
    • Easy or Hard Way?
  • The Webauthn Server
    • The Easy Way
      • Register Authenticators
      • Authenticate Your Users
    • The Hard Way
      • Register Authenticators
      • Authenticate Your Users
    • The Symfony Way
      • Entities with Doctrine
      • Firewall
  • Deep into the framework
    • Register Additional Authenticators
    • Debugging
    • User Verification
    • Attestation and Metadata Statement
    • Authenticator Selection Criteria
    • Authentication without username
    • Extensions
    • Token Binding
    • Authenticator Counter
    • Dealing with “localhost”
  • Migration
    • From v2.x to v3.0
Powered by GitBook
On this page
  • The Easy Way
  • The Hard Way
  • The Symfony Way

Was this helpful?

Edit on GitHub
Export as PDF
  1. Deep into the framework

Register Additional Authenticators

PreviousFirewallNextDebugging

Last updated 3 years ago

Was this helpful?

In some circumstances, you may need to register a new authenticator for a user e.g. when adding a new authenticator or when an administrator acts as another user to replace a lost device.

It is possible to perform this ceremony programmatically.

You can attach several authenticators to a user account. It is recommended in case of lost devices or if the user gets access on your application using multiple platforms (smartphone, laptop…).

The Easy Way

The procedure is the same as , except that you don’t have to save the user entity again.

The Hard Way

The procedure is the same as .

The Symfony Way

The following procedure is only available with the version 3.1.0 of the framework. For previous versions, please refer to the Hard Way above.

With a Symfony application, the fastest way for a user to register additional authenticators is to use the helper Webauthn\Bundle\Service\AuthenticatorRegistrationHelper provided by the bundle.

In the example below, we will create 2 routes: the first one to get the options, te second one to verify the authenticator response. These routes will return JSON responses, but you are free to use Twig templates or any of response type.

src/Controller/AddAuthenticatorController.php
<?php

declare(strict_types=1);

namespace App\Controller;

use Assert\Assertion;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Throwable;
use Webauthn\AuthenticatorAttestationResponseValidator;
use Webauthn\Bundle\Security\Storage\SessionStorage;
use Webauthn\Bundle\Service\AuthenticatorRegistrationHelper;
use Webauthn\Bundle\Service\PublicKeyCredentialCreationOptionsFactory;
use Webauthn\PublicKeyCredentialLoader;
use Webauthn\PublicKeyCredentialSourceRepository;
use Webauthn\PublicKeyCredentialUserEntity;

class AddAuthenticatorController extends AbstractController
{
    /**
     * @var AuthenticatorRegistrationHelper
     */
    private $helper;

    public function __construct(HttpMessageFactoryInterface $httpMessageFactory, ValidatorInterface $validator, SerializerInterface $serializer, PublicKeyCredentialCreationOptionsFactory $publicKeyCredentialCreationOptionsFactory, PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, PublicKeyCredentialLoader $publicKeyCredentialLoader, AuthenticatorAttestationResponseValidator $authenticatorAttestationResponseValidator, SessionStorage $optionsStorage)
    {
        $this->helper = new AuthenticatorRegistrationHelper(
            $publicKeyCredentialCreationOptionsFactory,
            $serializer,
            $validator,
            $publicKeyCredentialSourceRepository,
            $publicKeyCredentialLoader,
            $authenticatorAttestationResponseValidator,
            $optionsStorage,
            $httpMessageFactory
        );
    }

    /**
     * @Route(name="add_authenticator_options", path="/add/device/options", schemes={"https"}, methods={"POST"})
     */
    public function getOptions(Request $request): Response
    {
        try {
            $userEntity = $this->getUser();
            Assertion::isInstanceOf($userEntity, PublicKeyCredentialUserEntity::class, 'Invalid user');
            $publicKeyCredentialCreationOptions = $this->helper->generateOptions($userEntity, $request);

            return $this->json($publicKeyCredentialCreationOptions);
        } catch (Throwable $e) {
            return $this->json([
                'status' => 'failed',
                'errorMessage' => 'Invalid request',
            ], 400);
        }

    }

    /**
     * @Route(name="add_authenticator", path="/add/device", schemes={"https"}, methods={"POST"})
     */
    public function addAuthenticator(Request $request): Response
    {
        try {
            $userEntity = $this->getUser();
            Assertion::isInstanceOf($userEntity, PublicKeyCredentialUserEntity::class, 'Invalid user');
            $this->helper ->validateResponse($userEntity, $request);
            return $this->json([
                'status' => 'ok',
                'errorMessage' => '',
            ]);
        } catch (Throwable $e) {
            return $this->json([
                'status' => 'failed',
                'errorMessage' => 'Invalid request',
            ], 400);
        }
    }
}

If the current user is registering authenticators for another user (admin), the userEntity passed to the methods generateOptions and validateResponse must correspond to the target user.

This controller may be directly integrated in the bundle.

By default the options profile is default. You can change it setting the profile name as third argument of the method generateOptions.

$publicKeyCredentialCreationOptions = $this->helper->generateOptions(
    $userEntity,
    $request,
    'profile1'
);

As the user shall be authenticated to register a new authenticator, you should protect these routes in the security.yaml file.

config/packages/security.yaml
security:
    access_control:
        - { path: ^/add/device,  roles: IS_AUTHENTICATED_FULLY }
the one described in this page
the one described in this page