Only this pageAll pages
Powered by GitBook
1 of 55

v4.9

Loading...

The project

Loading...

Loading...

Loading...

Loading...

Webauthn In A Nutshell

Loading...

Loading...

Loading...

Loading...

Loading...

Prerequisites

Loading...

Loading...

Loading...

Loading...

Pure PHP

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Symfony Bundle

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Migration

Loading...

Symfony UX

Loading...

Loading...

User Verification

You can indicate the user verification requirements during the ceremonies by setting the value in your options.

use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialCreationOptions;

$publicKeyCredentialCreationOptions =
    PublicKeyCredentialCreationOptions::create(
        $rpEntity,
        $userEntity,
        $challenge,
        authenticatorSelection: AuthenticatorSelectionCriteria::create(AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED)
    )
;

Authenticator registration

User Authentication

// Public Key Credential Request Options
use Webauthn\PublicKeyCredentialRequestOptions;

$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create(
    random_bytes(32),
    userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_DISCOURAGED
);

Extensions

The following example is totally fictive. We will add an extension input loc=true to the request option object.

An Extension Output Checker (EOC) will check the extension output.

It must implement the interface Webauthn\AuthenticationExtensions\ExtensionOutputChecker and throw an exception of type Webauthn\AuthenticationExtension\ExtensionOutputError in case of error.

In the previous example, we asked for the location of the device and we expect to receive geolocation data in the extension output.

You can create as many EOC as needed. These services can be managed by a handler that will be injected to the Ceremony Step Manager Factory. Extensions will be automatically verified during the validation steps.

Extension Output Checker

Devices may ignore the extension inputs. The extension outputs are therefore not guaranteed.

Extension Output Checker Handler

<?php

declare(strict_types=1);

use Webauthn\AuthenticationExtensions\AuthenticationExtension;
use Webauthn\AuthenticationExtensions\AuthenticationExtensions;
use Webauthn\PublicKeyCredentialRequestOptions;

// Extensions
$extensions = AuthenticationExtensions::create([
    AuthenticationExtension::create('loc', true)
]);

// Public Key Credential Request Options
$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create(
    random_bytes(32), // Challenge
    extensions: $extensions
);
<?php

declare(strict_types=1);

namespace Acme\Extension;

use Webauthn\AuthenticationExtensions\ExtensionOutputChecker;
use Webauthn\AuthenticationExtensions\ExtensionOutputError;

final class LocationExtensionOutputChecker implements ExtensionOutputChecker
{
    public function check(AuthenticationExtensions $inputs, AuthenticationExtensions $outputs): void
    {
        if (!$inputs->has('loc') || $inputs->get('loc') !== true) {
            return;
        }

        if (!$outputs->has('loc')) {
            //You may simply return but here we consider it is a mandatory extension output.
            throw new ExtensionOutputError(
                $inputs->get('loc'),
                'The location of the device is missing'
            );
        }

        $location = $outputs->get('loc');
        //... Proceed with the output e.g. by logging the location of the device
        // or verifying it is in a specific area.
    }
}
<?php

declare(strict_types=1);

namespace Acme\Extension;

use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
use Webauthn\CeremonyStep\CeremonyStepManagerFactory;

$eocHandler = ExtensionOutputCheckerHandler::create();
$eocHander->add(new LocationExtensionOutputChecker());
// Add more EOC if needed.

$csmFactory = new CeremonyStepManagerFactory();
$csmFactory->setExtensionOutputCheckerHandler($eocHander);

Authenticate Your Users

During this step, your application will send a challenge to the list of registered devices of the user. The security token will resolve this challenge by adding information and digitally signing the data.

Assertion Request

To perform a user authentication using a security device, you need to instantiate a Webauthn\PublicKeyCredentialRequestOptions object.

Let’s say you want to authenticate the user we used earlier. This options object will need:

  • A challenge (random binary string)

  • The list with the allowed credentials (may be an option in certain circumstances)

Optionally, you can customize the following parameters:

  • A timeout

  • The Relying Party ID i.e. your application domain

  • The user verification requirement

  • Extensions

The PublicKeyCredentialRequestOptions object is designed to be easily serialized into a JSON object. This will ease the integration into an HTML page or through an API endpoint.

The user trying to authenticate must have registered at least one device. For this user, you have to get all Webauthn\PublicKeyCredentialDescriptor associated to his account.

Eligible authenticators are filtered and only capable of satisfying this requirement will interact with the user. Please refer to the for all possible values.

Please refer to the to know how to manage authentication extensions.

The way you receive this response is out of scope of this library. In the previous example, the data is part of the query string, but it can be done through a POST request body or a request header.

What you receive must be a JSON object that looks like as follows:

There are two steps to perform with this object:

This step is exactly the same as the one described in process.

Now we have a fully loaded Public Key Credential object, but we need now to make sure that:

  1. The authenticator response is of type AuthenticatorAssertionResponse

  2. This response is valid.

The first is easy to perform:

The second step is the verification against the Public Key Assertion Options we created earlier.

The Authenticator Assertion Response Validator service (variable $authenticatorAssertionResponseValidator) will check everything for you.

If no exception is thrown, the response is valid and you can continue the authentication of the user.

The timeout default value is set to null. If you want to set a value, pleaase read the following recommended behavior showed in the specification:

  • If the user verification is discouraged, timeout should be between 30 and 180 seconds

  • If the user verification is preferred or required, the range is 300 to 600 seconds (5 to 10 minutes)

Allowed Credentials

For usernameless authentication, please read the dedicated page. In this case no Public Key Credential Descriptors should be passed to the the options.

To prevent username enumeration, when the authentication process is performed using the username, it is highly recommended to continue the ceremony even is the user entity does not exist. The allowad credential list should be set with random data.

Example

User Verification

Extensions

Response Handling

Data Loading

Response Verification

User Verification page
Extension page
Load the data
Verify the loaded data against the assertion options set above
Public Key Credential Creation
use Webauthn\PublicKeyCredentialSource;

// We gather all registered authenticators for this user
// $publicKeyCredentialSourceRepository corresponds to your own service
// The purpose of the fictive method findAllForUserEntity is to return all credential source objects
// registered by the user.
$registeredAuthenticators = $publicKeyCredentialSourceRepository->findAllForUserEntity($userEntity);

// We don’t need the Credential Sources, just the associated Descriptors
$allowedCredentials = array_map(
    static function (PublicKeyCredentialSource $credential): PublicKeyCredentialDescriptor {
        return $credential->getPublicKeyCredentialDescriptor();
    },
    $registeredAuthenticators
);
<?php

declare(strict_types=1);

use Webauthn\PublicKeyCredentialDescriptor;
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialSource;

// List of registered PublicKeyCredentialDescriptor classes associated to the user
$registeredAuthenticators = $publicKeyCredentialSourceRepository->findAllForUserEntity($userEntity);
$allowedCredentials = array_map(
    static function (PublicKeyCredentialSource $credential): PublicKeyCredentialDescriptor {
        return $credential->getPublicKeyCredentialDescriptor();
    },
    $registeredAuthenticators
);

// Public Key Credential Request Options
$publicKeyCredentialRequestOptions =
    PublicKeyCredentialRequestOptions::create(
        random_bytes(32), // Challenge
        allowCredentials: $allowedCredentials
    )
;
<?php

declare(strict_types=1);

use Webauthn\PublicKeyCredentialDescriptor;
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialSource;

// Public Key Credential Request Options
$publicKeyCredentialRequestOptions =
    PublicKeyCredentialRequestOptions::create(
        random_bytes(32), // Challenge
        userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED
    )
;
<?php

declare(strict_types=1);

use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
use Webauthn\AuthenticationExtensions\AuthenticationExtension;

// Public Key Credential Request Options
$publicKeyCredentialRequestOptions =
    PublicKeyCredentialRequestOptions::create(
        random_bytes(32), // Challenge
        extensions: AuthenticationExtensionsClientInputs::create([
            AuthenticationExtension::create('loc', true),
            AuthenticationExtension::create('txAuthSimple', 'Please log in with a registered authenticator'),
        ])
    )
;
{
    "id":"KVb8CnwDjpgAo[…]op61BTLaa0tczXvz4JrQ23usxVHA8QJZi3L9GZLsAtkcVvWObA",
    "type":"public-key",
    "rawId":"KVb8CnwDjpgAo[…]rQ23usxVHA8QJZi3L9GZLsAtkcVvWObA==",
    "response":{
        "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJQbk1hVjBVTS[…]1iUkdHLUc4Y3BDSdGUifQ==",
        "authenticatorData":"Y0EWbxTqi9hWTO[…]4aust69iUIzlwBfwABDw==",
        "signature":"MEQCIHpmdruQLs[…]5uwbtlPNOFM2oTusx2eg==",
        "userHandle":""
    }
}
<?php

declare(strict_types=1);

$data = '
{
    "id":"KVb8CnwDjpgAo[…]op61BTLaa0tczXvz4JrQ23usxVHA8QJZi3L9GZLsAtkcVvWObA",
    "type":"public-key",
    "rawId":"KVb8CnwDjpgAo[…]rQ23usxVHA8QJZi3L9GZLsAtkcVvWObA==",
    "response":{
        "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJQbk1hVjBVTS[…]1iUkdHLUc4Y3BDSdGUifQ==",
        "attestationObject":"o2NmbXRmcGFja2VkZ2F0dFN0bXSj[…]YcGhf"
    }
}';

$publicKeyCredential = $serializer->deserialize($data, PublicKeyCredential::class, 'json');
<?php

declare(strict_types=1);

use Webauthn\AuthenticatorAssertionResponse;

if (!$publicKeyCredential->response instanceof AuthenticatorAssertionResponse) {
    //e.g. process here with a redirection to the public key login/MFA page. 
}
<?php

declare(strict_types=1);

$publicKeyCredentialSource = $publicKeyCredentialSourceRepository->findOneByCredentialId(
    $publicKeyCredential->rawId
);
if ($publicKeyCredentialSource === null) {
   // Throw an exception if the credential is not found.
   // It can also be rejected depending on your security policy (e.g. disabled by the user because of loss)
}

$publicKeyCredentialSource = $authenticatorAssertionResponseValidator->check(
    $publicKeyCredentialSource,
    $authenticatorAssertionResponse,
    $publicKeyCredentialRequestOptions,
    'my-application.com',
    $userEntity?->id // Should be `null` if the user entity is not known before this step
);

// Optional, but highly recommended, you can save the credential source as it may be modified
// during the verification process (counter may be higher).
$publicKeyCredentialSourceRepository->saveCredential($publicKeyCredentialSource);

Attestation and Metadata Statement

Important Notice: The request for an Attestation Statement is reserved for applications necessitating a high degree of reliability, such as those operated by banking or financial institutions, government agencies, etc. Exercise caution and ensure your application's context requires such a level of trust before proceeding.

Attestation Statement Support Manager

There are few steps to acheive. First, you have to add support classes for all attestation statement types into your Attestation Metatdata Manager.

For 4.5.0, the TPMAttestationStatementSupport class accepts a PSR-20 clock as argument. This argument will be mandatory for 5.0.0.

In the example below, we use symfony/clock component.

<?php

declare(strict_types=1);

use Cose\Algorithm\Manager;
use Cose\Algorithm\Signature\ECDSA;
use Cose\Algorithm\Signature\EdDSA;
use Cose\Algorithm\Signature\RSA;
use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport;
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
use Webauthn\AttestationStatement\FidoU2FAttestationStatementSupport;
use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
use Webauthn\AttestationStatement\PackedAttestationStatementSupport;
use Webauthn\AttestationStatement\TPMAttestationStatementSupport;
use Webauthn\AttestationStatement\AppleAttestationStatementSupport;
use Symfony\Component\Clock\NativeClock;

//We need a PSR-20 clock
$clock = new NativeClock();

// You normally already do this
$attestationStatementSupportManager = AttestationStatementSupportManager::create();
$attestationStatementSupportManager->add(NoneAttestationStatementSupport::create());

// Additional classes to add
$attestationStatementSupportManager->add(FidoU2FAttestationStatementSupport::create());
$attestationStatementSupportManager->add(AppleAttestationStatementSupport::create());

$attestationStatementSupportManager->add(AndroidKeyAttestationStatementSupport::create());
$attestationStatementSupportManager->add(TPMAttestationStatementSupport::create($clock));

// Cose Algorithm Manager
// The list of algorithm depends on the algorithm list you defined in your options
// You should use at least ES256 and RS256 algorithms that are widely used.
$coseAlgorithmManager = Manager::create();
$coseAlgorithmManager->add(ECDSA\ES256::create());
$coseAlgorithmManager->add(RSA\RS256::create());

$attestationStatementSupportManager->add(PackedAttestationStatementSupport::create($coseAlgorithmManager));

The Android SafetyNet Attestation API is deprecated. Full turndown is planned in June 2024. More information at

It is not described on this page, but available in the previous versions of the documentation.

Metadata Statement Repository

Then, you must prepare an Metadata Statement Repository. This service will manage all Metadata Statements depending on their sources (local storage or distant service).

Your Metadata Statement Repository must implement the interface Webauthn\MetadataService\MetadataStatementRepository that has only one method:

  • findOneByAAGUID(string $aaguid): this method retrieves the MetadataStatement object with AAGUID. It shall return null in case of the absence of the MDS.

To prevent the use of rogue MDS or deprecated by the manufacturers, Status Reports may be used. The Status Report Repository is a simple service that implements the interface Webauthn\MetadataService\StatusReportRepository with a unique method:

  • findStatusReportsByAAGUID(string $aaguid): this method returns a list of Status Reports for the given AAGUID.

The verification's success or failure depends on the state reported in the most recent Status Report.

When an attestation statement is received, a certificate chain is constructed. This chain combines certificates from the Metadata Service (MDS), which are trusted, with those provided by the authenticator, which are untrusted.

The Certificate Chain Validator is responsible for this task. The library provides the class Webauthn\MetadataService\CertificateChain\PhpCertificateChainValidator that requires a Symfony Http Client () and a PSR-20 Clock.

The services described above must be set to the . The CSM you will create will verifiy the attestation statement sent by the authenticator.

By default, no Attestation Statement is asked to the Authenticators (type = none). To change this behavior, you just have to set the corresponding parameter in the Webauthn\PublicKeyCredentialCreationOptions object.

There are 3 conveyance modes available using PHP constants provided by the class Webauthn\PublicKeyCredentialCreationOptions:

  • ATTESTATION_CONVEYANCE_PREFERENCE_NONE: the Relying Party is not interested in authenticator attestation (default)

  • ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT: the Relying Party prefers an attestation conveyance yielding verifiable attestation statements, but allows the client to decide how to obtain such attestation statements.

  • ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT

: the Relying Party wants to receive the attestation statement as generated by the authenticator.

The library does not provide any Metadata Statement Repository. It is up to you to select the MDS suitable for your application and store them in your database.

Status Report Repository

Certificate Chain Validator

Ceremony Step Manager Factory

Requesting Attestation Statement

for CRL verification
Ceremony Step Manager Factory
https://developer.android.com/training/safetynet/deprecation-timeline
<?php

use Webauthn\MetadataService\CertificateChain\PhpCertificateChainValidator;

$certificateChainValidator = PhpCertificateChainValidator::create(
    $httpClient,
    $clock
);
<?php

declare(strict_types=1);

use Webauthn\CeremonyStep\CeremonyStepManagerFactory;

$csmFactory = new CeremonyStepManagerFactory();
$csmFactory->setAttestationStatementSupportManager($attestationStatementSupportManager);
$csmFactory->enableMetadataStatementSupport(
    $metadataStatementRepository,
    $statusReportRepository,
    $certificateChainValidator,
);
<?php

use Webauthn\PublicKeyCredentialCreationOptions;

$publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create(
    $relyingParty,
    $userEntity,
    $challenge,
    attestation: PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT,
);

Advanced Behaviours

WebAuthn: Strong Authentication for your PHP applications

Seamless Integration with PHP Applications: Our Webauthn Documentation and Tutorials

Welcome to WebAuthn, the solution for strong authentication on the web. With WebAuthn, you can offer your users a secure, simple, and convenient authentication experience that seamlessly integrates with your PHP or Symfony application.

At Spomky-Labs, we are committed to making the web safer for all users. We provide free, libre, and open-source libraries and a Symfony bundle to help you implement strong authentication solutions for your websites and applications.

WebAuthn is an open-source technology developed by the World Wide Web Consortium (W3C) and is compatible with all modern web browsers. With WebAuthn and PHP, you can offer your users secure authentication without requiring them to remember complex passwords or provide sensitive personal information.

WebAuthn is supported by the FIDO Alliance, an organization working to promote open and interoperable strong authentication standards. You can learn more about the FIDO Alliance by visiting their website at https://fidoalliance.org/.

Explore our documentation to learn more about WebAuthn and how it can help you implement strong authentication in your PHP or Symfony application.

Installation

How to install the library or the Symfony bundle?

This framework contains several sub-packages that you don’t necessarily need. It is highly recommended to install what you need and not the whole framework.

The preferred way to install the library you need is to use composer:

composer require web-auth/webauthn-lib

For 4.8.0+, the following dependencies ar recommended and will be mandatory for 5.0.0

  • symfony/serializer

  • symfony/property-access

If you use Symfony Framework, you may be interested in the bundle and, optionally, the Stimulus component.

composer require web-auth/webauthn-symfony-bundle web-auth/webauthn-stimulus

Hereafter the dependency tree:

  • web-auth/webauthn-lib: this is the core library. This package can be used in any PHP project or within any popular framework (Laravel, CakePHP…)

  • web-auth/webauthn-symfony-bundle: this is a Symfony bundle that ease the integration of this authentication mechanism in your Symfony project.

The core library also depends on web-auth/cose-lib and web-auth/metadata-service. What are these dependencies?

web-auth/cose-lib contains several cipher algorithms and COSE key support to verify the digital signatures sent by the authenticators during the creation and authentication ceremonies. These algorithms are compliant with the . This library can be used by any other PHP projects. At the moment only signature algorithms are available, but it is planned to add encryption algorithms.

web-auth/metadata-service provides classes to support the . If you plan to use Attestation Statements during the creation ceremony, this service is mandatory. Please note that Attestation Statements decreases the user privacy as they may leak data that allow to identify a specific user. The use of Attestation Statements and this service are generally not recommended unless you REALLY need this information. This library can also be used by any other PHP projects.

Extensions

The mechanism for generating public key credentials, as well as requesting and generating Authentication assertions, can be extended to suit particular use cases. Each case is addressed by defining a registration extension.

Standard extensions are usually listed in the dedicated IANA Registry available at

symfony/property-info

  • phpdocumentor/reflection-docblock

  • RFC8152
    Fido Alliance Metadata Service

    This page is about an advanced feature. You can skip it if you are new with Webauthn

    This library is ready to handle extension inputs and outputs, but no concrete implementations are provided, except for the appid extension.

    It is up to you, depending on the extensions you want to support, to create the extension handlers.

    https://www.iana.org/assignments/webauthn/webauthn.xhtml

    Credential Source

    Authenticator details and how to manage them

    Credential Source Class

    After the registration of an authenticator, you will get a Public Key Credential Source object. It contains all the credential data needed to perform user authentication and much more:

    • The credential ID,

    • The public key,

    • The credential type (public_key),

    • The transports (USB, NFC, BLE, internal),

    • The attestation type,

    • The trust path,

    • The authenticator AAGUID,

    • The user handle (i.e. the user ID)

    • The authenticator counter,

    • Other UI data

    • ...

    Since 4.6.0 and except if you use the Symfony bundle, there is no interface to implement or abstract class to extend so that it should be easy to integrate it in your application.

    Input Validation

    The loaded data needs to be verified. The library will perform several actions to make sure the input you received is valid. This verification process is performed by a Ceremony Step Manager (CSM). The Webauthn Specification distinguish two types of ceremonies.

    To facilitate the creation of the CSM, a default factory is included. This factory requires no external services to function.

    These CSM services are meant to be used by Response Validators. On a similar way, there are two types of validators:

    • Authenticator Attestation Response Validator: used during the creation ceremony

    • Authenticator Assertion Response Validator: used during the request ceremony

    Bundle Installation

    This framework provides a Symfony bundle that will help you to use the components within your Symfony application.

    If you don't use Symfony Flex, you must register the bundle and route manually.

    The first steps are:

    • The creation of your

    • The creation of your .

    Credential Source Repository

    Whatever database you use (MySQL, pgSQL…), it is not necessary to create relationships between your users and the Credential Sources.

    You may also want to configure the other options offered by the bundle. Please refer to the
    .

    Now you have a fully configured bundle, you can protect your routes and manage the user registration and authenticatin through the Symfony Firewall.

    Implementations for other frameworks and web applications exist.

    As an example if you use Laravel, you may be interested in this project: https://github.com/asbiin/laravel-webauthn

    Installation

    With Symfony Flex

    As the recipes are third party ones not officially supported by Symfony, you will be asked to execute the recipe or not. When applied, the recipes will prompt a message such as WebAuthn Framework is ready.

    Without Symfony Flex

    Repositories

    Credential Source repository
    User Entity repository

    Firewall

    configuration references

    Extensions

    Extension Output Checker

    An Extension Output Checker will check the extension output.

    It must implement the interface Webauthn\AuthenticationExtensions\ExtensionOutputChecker and throw an exception of type Webauthn\AuthenticationExtension\ExtensionOutputError in case of error.

    Devices may ignore the extension inputs. The extension outputs are therefore not guaranteed.

    In the previous example, we asked for the location of the device and we expect to receive geolocation data in the extension output.

    The easiest way to manage that is by using the creation and request profiles.

    config/packages/webauthn.yaml
    webauthn:
        …
        creation_profiles:
            default:
                rp:
                    name: 'My Application'
                    id: 'example.com'
                extensions:
                    uvm: true
        request_profiles:
            default:
                rp_id: 'example.com'
                extensions:
                    uvm: true
    composer require web-auth/webauthn-symfony-bundle
    config/bundles.php
    <?php
    
    return [
        //...
        Webauthn\Bundle\WebauthnBundle::class => ['all' => true],
    ];
    config/routes/webauthn.php
    <?php
    
    declare(strict_types=1);
    
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->import('.', 'webauthn');
    };
    <?php
    
    declare(strict_types=1);
    
    namespace Acme\Extension;
    
    use Webauthn\AuthenticationExtensions\ExtensionOutputChecker;
    use Webauthn\AuthenticationExtensions\ExtensionOutputError;
    
    final class LocationExtensionOutputChecker
    {
        public function check(AuthenticationExtensionsClientInputs $inputs, AuthenticationExtensionsClientOutputs $outputs): void
        {
            if (!$inputs->has('uvm') || $inputs->get('uvm') !== true) {
                return;
            }
    
            if (!$outputs->has('uvm')) {
                //You may simply return but here we consider it is a mandatory extension output.
                throw new ExtensionOutputError(
                    $inputs->get('uvm'),
                    'The User Verification Method is missing'
                );
            }
    
            $uvm = $outputs->get('uvm');
            //... Proceed with the output
        }
    }
    The Authenticator Attestation Response Validator and Authenticator Assertion Response Validator services are directly used when receiving Authenticator Responses in order to register authenticators or authenticate users.
    <?php
    
    declare(strict_types=1);
    
    use Webauthn\CeremonyStep\CeremonyStepManagerFactory;
    
    $csmFactory = new CeremonyStepManagerFactory();
    
    $creationCSM = $csmFactory->creationCeremony();
    $requestCSM = $csmFactory->requestCeremony();

    Ceremony Step Manager Factory

    You can customize its behavior to fit the specific needs of your application by modifying the provided factory. Please refer to the dedicated pages for more information.

    • Counter Checker

    • Attestation Statement Support Manager

    Response Validators

    described in this page
    <?php
    
    declare(strict_types=1);
    
    use Webauthn\AuthenticatorAttestationResponseValidator;
    use Webauthn\AuthenticatorAssertionResponseValidator;
    
    $authenticatorAttestationResponseValidator = AuthenticatorAttestationResponseValidator::create(
        null, //Deprecated
        null, //Deprecated
        null, //Deprecated
        null, //Deprecated
        null, //Deprecated
        $creationCSM
    );
    $authenticatorAssertionResponseValidator = AuthenticatorAssertionResponseValidator::create(
        null, //Deprecated
        null, //Deprecated
        null, //Deprecated
        null, //Deprecated
        null, //Deprecated
        $requestCSM
    );
    
    // Also valid
    $authenticatorAttestationResponseValidator = AuthenticatorAttestationResponseValidator::create(
        ceremonyStepManager: $creationCSM
    );
    $authenticatorAssertionResponseValidator = AuthenticatorAssertionResponseValidator::create(
        ceremonyStepManager: $requestCSM
    );

    All null values below correspond to deprecated parameters. They will be removed in 5.0.0

    Input Loading

    In general, the data you receive is an encoded JSON object. The library provides utilities to convert the string into objects.

    To do so, you will need an Attestation Statement Support Manager and a Serializer.

    Attestation Statement Support Manager

    Authenticator Responses may contain an Attestation Statement. This attestation holds data regarding the authenticator depending on several factors such as its manufacturer and model, what you asked in the options, the capabilities of the browser or what the user allowed.

    Supported Attestation Statement Types

    The following attestation types are supported. Note that you should only use the none one unless you have specific needs described in the dedicated page.

    • none: no attestation is provided.

    • fido-u2f: for non-FIDO2 compatible devices (old FIDO / U2F security tokens).

    • packed: generally used by authenticators with limited resources (e.g. secure elements). It uses a very compact but still extensible encoding method.

    • android key: commonly used by old or disconnected Android devices.

    • android safety net: for new Android devices like smartphones (deprecated).

    • trusted platform module: for devices with built-in security chips.

    • apple: for Apple devices

    To convert Authenticator Responses from the encoded string to an object, you will need a Serializer. It only needs the Attestation Statement Support Manager created above.

    In general, the data you receive looks like as follows.

    If the data is correctly loaded, the variable $publicKeyCredential will be an instance of Webauthn\PublicKeyCredential. An exception is thrown in case of an error.

    Extension Output Checker Handler
    Algorithm Manager
    Metadata Statement Repository
    Status Report Repository
    Certification Chain Validator

    The Serializer

    Before 4.8.0, you were asked to create a PublicKeyCredentialLoader object. For 4.8.0, you can use a Symfony Serializer object. This will become the standard way to load data for 5.0.0.

    Loding Data

    Only this type of input is supported. If you receive other forms of data, please contact us.

    At this stage, the data is not verified.

    <?php
    
    declare(strict_types=1);
    
    use Webauthn\AttestationStatement\AttestationStatementSupportManager;
    use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
    
    // The manager will receive data to load and select the appropriate 
    $attestationStatementSupportManager = AttestationStatementSupportManager::create();
    $attestationStatementSupportManager->add(NoneAttestationStatementSupport::create());
    <?php
    
    declare(strict_types=1);
    
    use Webauthn\Denormalizer\WebauthnSerializerFactory;
    
    $factory = new WebauthnSerializerFactory($attestationStatementSupportManager);
    $serializer = $factory->create();
    {
        "id":"KVb8CnwDjpgAo[…]op61BTLaa0tczXvz4JrQ23usxVHA8QJZi3L9GZLsAtkcVvWObA",
        "type":"public-key",
        "rawId":"KVb8CnwDjpgAo[…]rQ23usxVHA8QJZi3L9GZLsAtkcVvWObA==",
        "response":{
            "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJQbk1hVjBVTS[…]1iUkdHLUc4Y3BDSdGUifQ==",
            "attestationObject":"o2NmbXRmcGFja2VkZ2F0dFN0bXSj[…]YcGhf"
        }
    }
    <?php
    
    declare(strict_types=1);
    
    use Webauthn\PublicKeyCredential;
    
    // $data corresponds to the JSON object showed above
    $publicKeyCredential = $serializer->deserialize(
        $data,
        PublicKeyCredential::class,
        'json'
    );

    Credential Source Repository

    Where the public keys and details are stored

    The Credential Source can be stored the way you want. As the Webauthn\PublicKeyCredentialSource class can be converted into JSON, it could be stored in a filesystem.

    It is up to you to create a credential source repository. This service shall implement Webauthn\Bundle\Repository\PublicKeyCredentialSourceRepositoryInterface

    Registration Capability

    By default, the User Entity Repository is not able to register any user account. You can add this behaviour by implementing the interface Webauthn\Bundle\Repository\CanRegisterUserEntity.

    Doctrine Repository

    In general, Symfony applications use Doctrine. That is why the bundle provides a way to use Doctrine as storage system.

    The Doctrine Entity

    Hereafter an example of an entity.

    This is the most simple example. Feel free to add custom fields that fits on your needs e.g. created_at or is_revoked.

    To ease the integration into your application, the bundle provides a concrete class that you can extend.

    This repository should be declared as a Symfony service.

    Firewall

    How to register and authenticate my users?

    To authenticate or register your users with Symfony, the best and easiest way is to use the Security Bundle. First, install that bundle and follow the instructions given by .

    At the end of the installation and configuration, you should have a config/packages/security.yaml file that looks like as follow:

    To enable the user authentication, you just have to declare the webauthn authenticator if the appropriate firewall (here main).

    As you have noticed, there is nothing to configure to have a fully functional firewall. The firewall routes are automatically created for you. They are namely:

    Authentication without username

    With Webauthn, it is possible to authenticate a user without username. This behavior implies several constraints:

    1. During the registration of the authenticator, a ,

    2. The user verification is required,

    3. The list of allowed authenticators must be empty

    Fake Credentials

    In order to prevent username enumeration, random credentials are set when a username is passed but no user entity is found.

    A very simple service is provided. If you want to change the way the fake credentials are generated, you can create a custom service. The service shall implement the Webauthn\FakeCredentialGenerator interface.

    Then, declare this service in the container and use it in your bundle configuration.

    Do not forget to update your database schema!

    The Repository

    In this following example, we extend that class and add a method to get all credentials for a specific user handle. Feel free to add your own methods.

    We must override the method saveCredentialSource because we may receive Webauthn\PublicKeyCredentialSource objects instead of App\Entity\WebauthnCredential.

    With Symfony autowiring and autoconfiguration, this is usually done automatically

    src/CustomCredentialGenerator.php
    <?php
    
    namespace App;
    
    use Webauthn\FakeCredentialGenerator;
    use Webauthn\PublicKeyCredentialDescriptor;
    
    final readonly class CustomCredentialGenerator implements FakeCredentialGenerator
    {
        /**
         * @return PublicKeyCredentialDescriptor[]
         */
        public function generate(Request $request, string $username): array
        {
            // Generate your list of fake credentials.
            // Note that for a given username you should always return the same credentials.
        }
    }
    config/packages/webauthn.yaml
    webauthn:
        fake_credential_generator: App\CustomCredentialGenerator
    /login/options: to create the request options (POST only)
  • /login: to submit the assertion response (POST only)

  • If you need, you can customize those endpoints.

    By default, the default profile is used (see request_profiles in the Configuration References). You may have created a request profile in the bundle configuration. You can use this profile instead of the default one.

    The user registration can also by managed by the firewall. It is disabled by default. If you want that feature, please enable it:

    The firewall routes are automatically created for you. They are namely:

    • /register/options: to create the creation options (POST only)

    • /register: to submit the attestation response (POST only)

    You should also ensure to allow anonymous users to contact those endpoints.

    The security token returned by the firewall sets some attributes depending on the assertion and the capabilities of the authenticator. The attributes are:

    • IS_USER_PRESENT: the user was present during the authentication ceremony. This attribute is usually set to true by authenticators,

    • IS_USER_VERIFIED: the user was verified by the authenticator. Verification may be performed by several means including biometrics ones (fingerprint, iris, facial recognition…).

    You can then set constraints to the access controls. In the example below, the /admin path can be reached by users with the role ROLE_ADMIN and that have been verified during the ceremony.

    You can customize the responses returned by the firewall by using a custom handler. This could be useful when you want to return additional information to your application.

    There are 4 types of responses and handlers:

    • Request options: options returned during the authentication ceremony,

    • Creation options: options returned during the registration ceremony,

    • Authentication Success,

    • Authentication Failure,

    This handler is called when a client sends a valid POST request to the options_path during the authentication process. The default Request Options Handler is Webauthn\Bundle\Security\Handler\DefaultRequestOptionsHandler. It returns a JSON Response with the Public Key Credential Request Options objects in its body.

    Your custom handler has to implement the interface Webauthn\Bundle\Security\Handler\RequestOptionsHandler and be declared as a service.

    When done, you can set your new service in the firewall configuration:

    This handler is very similar to the previous one, except that it is called during the registration of a new user and has to implement the interface Webauthn\Bundle\Security\Handler\CreationOptionsHandler.

    This handler is called when a client sends a valid assertion from the authenticator. The default handler is Webauthn\Bundle\Security\Handler\DefaultSuccessHandler.

    Your custom handler has to implement the interface Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface and be declared as a container service.

    When done, you can set your new service in the firewall configuration:

    This handler is called when an error occurred during the authentication process. The default handler is Webauthn\Bundle\Security\Handler\DefaultFailureHandler.

    Your custom handler has to implement the interface Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface and be declared as a container service.

    When done, you can set your new service in the firewall configuration:

    Security Bundle

    User Authentication

    the official documentation

    Request Profile

    User Registration

    Authentication Attributes

    Response Handlers

    Request Options Handler

    Creation Options Handler

    Authentication Success Handler

    Authentication Failure Handler

    Selection criteria for the registration of the authenticator:

    The Request Options:

    In case of failure, you should continue with the standard authentication process i.e. by asking the username of the user.

    Resident Key must have been asked
    use Webauthn\AuthenticatorSelectionCriteria;
    use Webauthn\PublicKeyCredentialCreationOptions;
    
    
    $authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create(
        userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED,
        residentKey: AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_REQUIRED,
    );
    
    $publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create(
        $rpEntity,
        $userEntity,
        $challenge,
        authenticatorSelection: $authenticatorSelectionCriteria
    );

    The default values for the user verification and the resident key are set to preferred and resident keys may be created if the authenticator is compatible. This means that some users may log in without username.

    App/Entity/WebauthnCredential.php
    <?php
    
    declare(strict_types=1);
    
    namespace App\Entity;
    
    use App\Repository\PublicKeyCredentialSourceRepository;
    use Doctrine\ORM\Mapping\Entity;
    use Doctrine\ORM\Mapping\Column;
    use Doctrine\ORM\Mapping\GeneratedValue;
    use Doctrine\ORM\Mapping\Id;
    use Doctrine\ORM\Mapping\Table;
    use Symfony\Component\Uid\AbstractUid;
    use Symfony\Component\Uid\Ulid;
    use Webauthn\PublicKeyCredentialSource;
    use Webauthn\TrustPath\TrustPath;
    
    #[Table(name: "webauthn_credentials")]
    #[Entity(repositoryClass: WebauthnCredentialRepository::class)]
    class WebauthnCredential extends PublicKeyCredentialSource
    {
        #[Id]
        #[Column(unique: true)]
        #[GeneratedValue(strategy: "NONE")]
        private string $id;
    
        public function __construct(
            string $publicKeyCredentialId,
            string $type,
            array $transports,
            string $attestationType,
            TrustPath $trustPath,
            AbstractUid $aaguid,
            string $credentialPublicKey,
            string $userHandle,
            int $counter
        )
        {
            $this->id = Ulid::generate();
            parent::__construct($publicKeyCredentialId, $type, $transports, $attestationType, $trustPath, $aaguid, $credentialPublicKey, $userHandle, $counter);
        }
    
        public function getId(): string
        {
            return $this->id;
        }
    }
    App/Repository/WebauthnCredentialRepository.php
    <?php
    
    declare(strict_types=1);
    
    namespace App\Repository;
    
    use App\Entity\PublicKeyCredentialSource;
    use Doctrine\Persistence\ManagerRegistry;
    use Webauthn\Bundle\Repository\DoctrineCredentialSourceRepository;
    use Webauthn\PublicKeyCredentialSource;
    
    final class WebauthnCredentialRepository extends DoctrineCredentialSourceRepository
    {
        public function __construct(ManagerRegistry $registry)
        {
            parent::__construct($registry, WebauthnCredential::class);
        }
    
        public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void
        {
            if (!$publicKeyCredentialSource instanceof WebauthnCredential) {
                $publicKeyCredentialSource = new WebauthnCredential(
                    $publicKeyCredentialSource->publicKeyCredentialId,
                    $publicKeyCredentialSource->type,
                    $publicKeyCredentialSource->transports,
                    $publicKeyCredentialSource->attestationType,
                    $publicKeyCredentialSource->trustPath,
                    $publicKeyCredentialSource->aaguid,
                    $publicKeyCredentialSource->credentialPublicKey,
                    $publicKeyCredentialSource->userHandle,
                    $publicKeyCredentialSource->counter
                );
            }
            parent::saveCredentialSource($publicKeyCredentialSource);
        }
    }
    
    config/services.yaml
    services:
        App\Repository\WebauthnCredentialRepository: ~
    config/packages/security.yaml
    security:
        providers:
            default:
                id: App\Security\UserProvider
        firewalls:
            main:
                logout:
                    path: 'logout'
                ...
    config/packages/security.yaml
    security:
        providers:
            default:
                id: App\Security\UserProvider
        firewalls:
            main:
                webauthn: ~
                logout:
                    path: '/logout'
        access_control:
            - { path: ^/login,  roles: PUBLIC_ACCESS, requires_channel: 'https'}
            - { path: ^/logout,  roles: PUBLIC_ACCESS}
    config/packages/security.yaml
    security:
        firewalls:
            main:
                webauthn:
                    authentication:
                        routes:
                            options_path: '/assertion/options'
                            result_path: '/assertion/result'
    
        access_control:
            - { path: ^/assertion,  roles: PUBLIC_ACCESS, requires_channel: 'https'}
    config/packages/security.yaml
    security:
        firewalls:
            main:
                webauthn:
                    authentication:
                        profile: 'acme'
    config/packages/security.yaml
    security:
        firewalls:
            main:
                webauthn:
                    registration:
                        enabled: true
    config/packages/security.yaml
    security:
        access_control:
            - { path: ^/register,  roles: PUBLIC_ACCESS}
    config/packages/security.yaml
    security:
        access_control:
            - { path: ^/admin,  roles: [ROLE_ADMIN, IS_USER_VERIFIED]}
    config/packages/security.yaml
    security:
        firewalls:
            main:
                webauthn:
                    authentication:
                        options_handler: 'App\Handler\MyCustomRequestOptionsHandler'
    config/packages/security.yaml
    ecurity:
        firewalls:
            main:
                webauthn:
                    registration:
                        options_handler: 'App\Handler\MyCustomRequestOptionsHandler'
    config/packages/security.yaml
    security:
        firewalls:
            main:
                webauthn:
                    success_handler: 'App\Handler\MyCustomAuthenticationSuccessHandler'
    config/packages/security.yaml
    security:
        firewalls:
            main:
                webauthn:
                    failure_handler: 'App\Handler\MyCustomAuthenticationFailureHandler'
    // Public Key Credential Request Options
    
    use Webauthn\PublicKeyCredentialRequestOptions;
    
    $publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create(
        random_bytes(32),
        userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED
    );

    Authenticator Selection Criteria

    By default, any type of authenticator can be used by your users and interact with you application. In certain circumstances, you may need to select specific authenticators e.g. when user verification is required.

    The Webauthn API and this library allow you to define a set of options to disallow the registration of authenticators that do not fulfill with the conditions.

    The class Webauthn\AuthenticatorSelectionCriteria is designed for this purpose. It is used when generating the Webauthn\PublicKeyCredentialCreationOptions object.

    Available Criteria

    Authenticator Attachment Modality

    You can indicate if the authenticator must be attached to the client (platform authenticator i.e. it is usually not removable from the client device) or must be detached (roaming authenticator).

    Possible values are:

    • AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE: there is no requirement (default value),

    • AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM: the authenticator must be attached,

    • AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM: must be a roaming authenticator.

    A primary use case for platform authenticators is to register a particular client device as a "trusted device" for future authentication. This gives the user the convenience benefit of not needing a roaming authenticator, e.g., the user will not have to dig around in their pocket for their key fob or phone.

    When this criterion is set to true, a Public Key Credential Source will be stored in the authenticator, client or client device. Such storage requires an authenticator capable to store such a resident credential.

    This criterion is needed if you want to .

    What is Webauthn?

    Overview of the framework

    Webauthn defines an API enabling the creation and use of strong, attested, scoped, public key-based credentials by web applications, for the purpose of strongly authenticating users.

    The complete specification can be found on the W3C dedicated page.

    This framework contains PHP libraries and Symfony bundle to allow developers to integrate that authentication mechanism into their web applications.

    Class, Constant and Property Names

    Naming things may be complicated. That’s why the following rule applies on the whole framework: the name of classes, constants and properties are identical to the ones you will find in the specification.

    As an example, the section 5.2.2 “Web Authentication Assertion” shows an object named AuthenticatorAssertionResponse that extends AuthenticatorResponse with the following properties:

    • authenticatorData

    • signature

    • userHandle

      You will find in the PHP class provided by the library.

    • Attestation Types

      • Empty

      • Basic

      • Self

    • Attestation Formats

      • FIDO U2F

      • Packed

      • TPM

    As of January 2023, our framework supports every authenticator with full feature and algorithm support, achieving a 100% success rate across all tests. Official FIDO Alliance testing tools have validated its compliance.

    We ensure continuous conformity through rigorous unit and functional testing throughout the development process.

    I bring solutions to your problems and answer your questions.

    If you really love that project, and the work I have done or if you want I prioritize your issues, then !

    Requests for new features, bug fixed and all other ideas to make this framework useful are welcome.

    If you feel comfortable writing code, you could try to fix or .

    Do not forget to follow .

    Contributing

    You have just found a bug?

    First of all, thank you for contributing.

    Bugs or feature requests can be posted online on the GitHub issues section of the project.

    Few rules to ease code reviews and merges:

    • You MUST follow the PSR-12 for coding standards.

    • You MUST use the PSR-20 to get the time.

    • You MUST run the test suite (see below).

    • You MUST write (or update) tests when bugs are fixed or features are added.

    • You SHOULD write documentation.

    • You MAY follow the and .

    We use the following branching workflow:

    • Each minor version has a dedicated branch (e.g. 1.1.x, 1.2.x, 2.0.x, 2.1.x…)

    • The default branch is set to the last minor version (e.g. v2.1.x).

    To contribute use , please, write commit messages that make sense, and rebase your branch before submitting your PR.

    • install composer: curl -s http://getcomposer.org/installer | php

    • install dependencies: php composer.phar install

    • run tests: vendor/bin/phpunit

    Private CA

  • Anonymization CA

  • Android Key

  • Android Safetynet (deprecated)

  • Apple

  • Cose Algorithms

    • RS1, RS256, RS384, RS512

    • PS256, PS384, PS512

    • ES256, ES256K, ES384, ES512

    • ED25519

  • Extensions

    • Supported (not fully tested)

    • appid extension (compatibility with FIDO U2F authenticator

  • Supported features

    Note that Elliptic Curve Direct Anonymous Attestation (ECDAA) is deprecated and not supported

    The Token Binding support feature is now deprecated as not part of the latest specification version

    Compatible Authenticators

    See https://github.com/herrjemand/awesome-webauthn/pull/61 and https://github.com/herrjemand/awesome-webauthn#server-libs

    Support

    Contributing

    If you think you have found a security issue, DO NOT open an issue. You MUST submit your issue here.

    EXACTLY the same structure
    you can help me out for a couple of🍻 or more
    opened issues where help is wanted
    those that are easy to fix
    these best practices
    Please select the correct branch when submitting a PR
    • If it is a bug fix, please use the version first major release (1.0.x, 2.0.x, 3.0.x...)

    • If it is a new feature, please use the last minor release

    Run test suite

    PSR-5
    PSR-19
    Pull Requests

    Javascript

    You will interact with the authenticators through an HTML page and Javascript using the Webauthn API.

    We highly recommend the use of @simplewebauthn/browser. This library provides lots of easy and useful features and it fully compliant with the specification.

    If you use the Symfony UX, you may be interested in the Stimulus Controller.

    Note that is mandatory to use the HTTPS scheme to use Webauthn otherwise it will not work. This is also mandatory for localhost.

    Advanced Behaviors

    Resident Key

    To be written

    authenticate users without username

    Authenticator Counter

    The authenticators may have an internal counter. This feature is very helpful to detect cloned devices.

    The default behaviour is to reject the assertions. This behaviour might cause some troubles as it could reject the real device whilst the fake one can continue to be used.

    It is therefore required to go deeper in the protection of your application by logging the error and locking the associated account.

    To do so , you have to create a custom Counter Checker and inject it to your Authenticator Assertion Response Validator. The checker must implement the interface Webauthn\Counter\CounterChecker.

    <?php
    
    declare(strict_types=1);
    
    
    namespace App\Service;
    
    use App\SecuritySystem;
    use Assert\Assertion;
    use Throwable;
    use Webauthn\PublicKeyCredentialSource;
    
    final class CustomCounterChecker implements CounterChecker
    {
        private $securitySystem;
    
        public function __construct(SecuritySystem $securitySystem)
        {
            $this->securitySystem = $securitySystem ;
        }
    
        public function check(PublicKeyCredentialSource $publicKeyCredentialSource, int $currentCounter): void
        {
            try {
                Assertion::greaterThan($currentCounter, $publicKeyCredentialSource->getCounter(), 'Invalid counter.');
            } catch (Throwable $throwable) {
                $this->securitySystem->fakeDeviceDetected($publicKeyCredentialSource);
                throw $throwable;
            }
        }
    }

    The Counter Checker service can be injected to your Ceremony Step Manager Factory.

    config/packages/webauthn.yaml
    webauthn:
        …
        creation_profiles:
            default:
                rp:
                    name: 'My Application'
                    id: 'example.com'
                authenticator_selection_criteria:
                    attachment_mode: !php/const Webauthn\AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE
                    require_resident_key: false
                    user_verification: !php/const Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED
    <?php
    
    declare(strict_types=1);
    
    use Webauthn\CeremonyStep\CeremonyStepManagerFactory;
    
    $csmFactory = new CeremonyStepManagerFactory();
    $csmFactory->setCounterChecker($customCounterChecker);

    Web Browser Support

    Adoption by web browsers

    Webauthn is now supported by all main web browsers:

    • Mozilla Firefox 60+ and Firefox for Android 68+

    • Google Chrome 67+

    • Microsoft EDGE 18+ and Microsoft EDGE Chromium 79+

    • Opera 54+

    • Safari 13+ and iOS Safari 13.3+

    • Android Browser 76+

    For more information and limitation on these browsers, please have a look at the following page:

    Metadata Statement

    During the Attestation Ceremony (i.e. the registration of the authenticator), you can ask for the Attestation Statement of the authenticator. The Attestation Statements have one of the following types:

    • None (none): no Attestation Statement is provided

    • Basic Attestation (basic): Authenticator’s attestation key pair is specific to an authenticator model.

    https://caniuse.com/#feat=webauthn

    Surrogate Basic Attestation (or Self Attestation - self): Authenticators that have no specific attestation key use the credential private key to create the attestation signature

  • Attestation CA (AttCA): Authenticators are based on a Trusted Platform Module (TPM). They can generate multiple attestation identity key pairs (AIK) and requests an Attestation CA to issue an AIK certificate for each.

  • Anonymization CA (AnonCA): Authenticators use an Anonymization CA, which dynamically generates per-credential attestation certificates such that the attestation statements presented to Relying Parties do not provide uniquely identifiable information.

  • The Metadata Statements are issued by the manufacturers of the authenticators. These statements contain details about the authenticators (supported algorithms, biometric capabilities...) and all the necessary information to verify the Attestation Statements generated during the attestation ceremony.

    There are several possible sources to get these Metadata Statements. The main source is the FIDO Alliance Metadata Service that allows fetching statements on-demand, but some of them may be provided by other means.

    This page is about an advanced feature. You can skip it if you are new with Webauthn

    Disclaimer: you should not ask for the Attestation Statement unless you want to play with it or you are working on an application that requires a high level of trust (e.g. Banking/Financial Company, Government Agency...).

    Attestation Statement

    Metadata Statement

    The FIDO Alliance Metadata Service provides a limited number of Metadata Statements. It is mandatory to get the statement from the manufacturer of your authenticators otherwise the Attestation Statement won't be verified and the Attestation Ceremony will fail.

    From 4.x to 5.0

    Step-by-step guide for migrating from 4.x to 5.0

    This project follows the Semantic Versioning principles and, contrary to upgrade a minor version (where the middle number changes) where no difficulty should be encountered, upgrade a major version (where the first number changes) is subject to significant modifications.

    Update the libraries

    First of all, you have to make sure you are using the last 4.x release (4.8.0 at the time of writing).

    In addition, you have to make sure you are using PHP 8.3+.

    If you explicitly require web-auth/metadata-service, please remove it. All classes are now part of the main library

    Spot deprecations

    Next, you have to verify you don’t use any deprecated class, interface, method or property. If you have PHPUnit tests, you can easily get the list of deprecation used in your application.

    PSR-20 Clock

    In previous versions, the classes that requires time used the PHP time function directly. It is now required to use a PSR-20 Clock implementation and pass it to the classes.

    • Webauthn\MetadataService\CertificateChain\PhpCertificateChainValidator

    For version 3.2.0+ and the Symfony Bundle, an internal implementation service named jose.internal_clock existed and is removed.

    The Android SafetyNet API is deprecated and is completely removed in 5.0.

    All references to token binding are deprecated. This functionality is not supported anymore as removed from the latest Webauthn spectification versions.

    All references to the ECDAA Attestation Statement type are deprecated. This functionality is not supported anymore as removed from the latest Webauthn spectification versions.

    • Constant AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_NONE: please use AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_NO_PREFERENCE instead

    This class is now deprecated. Please use Webauthn\MetadataService\CertificateChainChecker\PhpCertificateChainValidator instead or a class that implements Webauthn\MetadataService\CertificateChain\CertificateChainValidator.

    As these values are redundant, the Id property is deprecated in 4.9.0 and will be removed in 5.0.0.

    This class is removed in 5.0. You should use Symfony Serializer or create a dedicated serializer using Webauthn\Denormalizer\WebauthnSerializerFactory.

    This interface is deprecated and removed. There is no replacement as it became useless for the library. The Symfony bundle uses its own interface Webauthn\Bundle\Repository you are asked to use in the Symfony context.

    The PSR-17 and PSR-18 are not supported anymore. The library uses Symfony Http Client instead. A class is provided to help you to continue using PSR-* compatible libraries: Webauthn\MetadataService\Psr18HttpClient. This class is very basic and can be enhanced or overridden at will.

    The following events are removed in favor of events located in the library namespace:

    • Webauthn\Bundle\Event\AuthenticatorAssertionResponseValidationFailedEvent

    • Webauthn\Bundle\Event\AuthenticatorAssertionResponseValidationSucceededEvent

    • Webauthn\Bundle\Event\AuthenticatorAttestationResponseValidationFailedEvent

    The following services are removed:

    • webauthn.cose.algoritm.* (because of a typo)

    • Webauthn\PublicKeyCredentialLoader

    • Webauthn\PublicKeyCredentialSourceRepository

    • Added:

      • symfony/clock

      • symfony/serializer

    The following options are removed:

    • webauthn.http_message_factory

    • webauthn.token_binding_support_handler

    • webauthn.creation_profiles[x].attachment_mode

    When deprecations are removed, you can upgrade the libraries. In your composer.json, change all web-auth/* dependencies from ^4.x to ^5.0. When done, execute composer update.

    When the libraries are up to date, you will need to perform a database migration.

    The field type for transports and other_ui changed from array to json (array is now deprecated).

    Hereafter an example of a Postgresql command:

    Also, the trust path type key is not needed anymore and can be removed:

    If you want to see all modifications at once, please .

    User Verification

    The easiest way to manage the user verification requirements in your Symfony application is by using the creation and request profiles.

    Webauthn\Bundle\Event\AuthenticatorAttestationResponseValidationSucceededEvent

    Webauthn\TokenBinding\IgnoreTokenBindingHandler
  • Webauthn\TokenBinding\SecTokenBindingHandler

  • Webauthn\TokenBinding\TokenBindingNotSupportedHandler

  • symfony/property-access
  • symfony/property-info

  • phpdocumentor/reflection-docblock

  • Bumped:

    • PHP: >=8.2

    • symfony/*: ^6.4 or ^7.0

  • Removed:

    • lcobucci/clock

  • Android SafetyNet

    Token Binding

    ECDAA

    Webauthn\AuthenticatorSelectionCriteria

    Webauthn\CertificateToolbox

    PublicKeyCredential Id and rawId

    Webauthn\PublicKeyCredentialLoader

    Webauthn\PublicKeyCredentialSourceRepository

    Symfony Http Client

    Events

    Services

    Dependency Changes:

    Configuration Files

    Upgrade the libraries

    This may also update other dependencies. You can list upgradable libraries by calling composer outdated. Please make sure these libraries do not impact your upgrade.

    Doctrine Migration

    These actions are only required if you use Doctrine and your public key credential source entity extends the one provided by the library.

    All Modifications In A Row

    https://developer.android.com/privacy-and-security/safetynet/deprecation-timeline
    have a look at this page
    config/packages/webauthn.yaml
    webauthn:
        …
        creation_profiles:
            default:
                rp:
                    name: 'My Application'
                    id: 'example.com'
                authenticator_selection_criteria:
                    user_verification: !php/const Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED
        request_profiles:
            default:
                rp_id: 'example.com'
                user_verification: !php/const Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED
    ALTER TABLE [/*TABLE NAME HERE*/] ALTER transports TYPE JSON USING transports::JSON;
    ALTER TABLE [/*TABLE NAME HERE*/] ALTER other_ui TYPE JSON USING other_ui::JSON;
    UPDATE pk_credential_sources SET trust_path = trust_path::jsonb - \'type\';

    Ceremonies

    Registration and Authentication process overview

    In the Webauthn context, there are two ceremonies:

    • The attestation ceremony or creation ceremony: associates an authenticator to a user account

    • The assertion ceremony or request ceremony: used for the authentication of a user.

    For both ceremonies, there are two steps to perform:

    1. The creation of options: these options are sent to the authenticator and indicate what to do and how.

    2. The response of the authenticator: after the user interacted with the authenticator, the authenticator computes a response that has to be verified.

    Depending on the options and the capabilities of the authenticator, the user interaction may differ. It can be a simple touch on a button or a complete authentication using biometric means (PIN code, fingerprint, facial recognition…).

    This ceremony aims at registering an authenticator. It can be used during the creation of a new user account or when an existing user wants to add an additional authenticator.

    This ceremony aims at authenticating a user. The user will be asked to interact with one of its authenticators. Additional authentication means, such as PIN code or fingerprint, may be required.

    Attestation ceremony

    Assertion ceremony

    The attestation ceremony
    The assertion ceremony

    Debugging

    If you have troubles during the development of your application or if you want to keep track of every critical/error messages in production, you can use a PSR-3 compatible logger.

    Several classes implement the interface Webauthn\MetadataService\CanLogData. This interface allows setting a PSR-3 logger instance.

    User Entity

    It's all about users

    User Entity Class

    A User Entity object represents a user in the Webauthn context. It has the following constraints:

    • The user ID must be unique and must be a string,

    • The username must be unique,

    Hereafter a minimalist example of user entity:

    <?php
    
    use Webauthn\PublicKeyCredentialUserEntity;
    
    $userEntity = PublicKeyCredentialUserEntity::create(
        'john.doe',                             // Username
        'ea4e7b55-d8d0-4c7e-bbfa-78ca96ec574c', // ID
        'John Doe'                              // Display name
    );

    The username can be composed of any displayable characters, including emojis. Username "😝🥰😔" is perfectly valid.

    Developers should not add rules that prevent users from choosing the username they want.

    For privacy reasons, it is not recommended using the e-mail as username.

    As for the rp Entity, the User Entity may have an icon. This icon must also be secured.

    Except if you use the Symfony bundle, there is no interface to implement or abstract class to extend so that it should be easy to integrate it in your application. You may already have a user repository.

    The Relying Party

    aka the application you are interacting with

    The Relying Party (or rp) corresponds to the application that will ask for the user to interact with the authenticator.

    The library provides a simple class to handle the rp information: Webauthn\PublicKeyCredentialRpEntity.

    <?php
    
    use Webauthn\PublicKeyCredentialRpEntity;
    
    $rpEntity = PublicKeyCredentialRpEntity::create(
        'ACME Webauthn Server' // The application name
    );

    This $rpEntity object will be useful for the next steps.

    Relying Party ID

    In the example above, we created a simple relying party object with it’s name. The relying party may also have an ID that corresponds to the domain applicable for that rp. By default, the relying party ID is null i.e. the current domain will be used.

    It may be useful to specify the rp ID, especially if your application has several sub-domains. The rp ID can be set during the creation of the object as 2nd constructor parameter.

    The rp ID shall be the domain of the application without the scheme, userinfo, port, path, user…. IP addresses are not allowed either.

    The Relying Party ID should be determined depending on the common URLs for your web application.

    If you have a web application that can be reached at (for mobiles) and or (for other devices), your Relying Party ID should be my-app.com.

    If the domain is shared between sub-projects, the rp ID should be limited to that sub-projects.

    For example, a web site is located at https://(www.)site1.host.com and another at https://(www.)site2.host.com, then the Relying Party IDs should be site1.host.com and site2.host.com respectively. If you set host.com, there is a risk that users from site1.host.com can log in at site2.host.com.

    Your application may also have a logo. You can indicate this logo as third argument. Please note that for safety reason this icon is a priori authenticated URL i.e. an image that uses the data scheme.

    Integration

    Consider the following login form.

    <form>
        <label for="username">Username</label>
        <input name="username" type="text" id="username" placeholder="Type your username here" autocomplete="username">
        
        <label for="password">Password</label>
        <input name="password" type="password" id="password" placeholder="Type your password here" autocomplete="password">
        
        <button type="submit">
            Sign in
        </button>
    </form>

    First step is to remove the password field that is no longer needed. In addition, we can indicate the autocomplete method is webauthn; this helps browser understanding the purpose of this field.

    <form>
        <label for="username">Username</label>
        <input name="username" type="text" id="username" placeholder="Type your username here" autocomplete="username webauthn">
    
        <button type="submit">
            Sign in
        </button>
    </form>

    We now have only two Twig functions to call: stimulus_controller and stimulus_action.

    • The first one is placed on the form level;

    • The latter on the button.

    The Stimulus Controller should be configured to fits on your needs. In particular, the routes to the options and authenticator result. The route names used below are automatically created by the firewall from the bundle package. By using these values, we make sure the routes are always in line with the firewall configuration.

    Authenticator Counter

    The authenticators may have an internal counter. This feature is very helpful to detect cloned devices.

    The default behaviour is to reject the assertions. This might cause some troubles as it could reject the real device whilst the fake one can continue to be used. You may also want to log the error, warn administrators or lock the associated user account.

    To do so , you have to create a custom Counter Checker and inject it to your Authenticator Assertion Response Validator. The checker must implement the interface Webauthn\Counter\CounterChecker.

    The following example is fictive and show how to lock a user, log the error and throw an exception.

    Authenticators

    What is an authenticator?

    An Authenticator is a cryptographic entity used to generate a public key credential and registered by a Relying Party (i.e. an application). This public key is used to authenticate by potentially verifying a user in the form of an authentication assertion and other data.

    Authenticators may have additional features such as PIN code or biometric sensors (fingerprint, facial recognition…) that offer user verification.

    The roaming authenticator may have different forms. The most common form is a USB device the user plugs into its computer. It can be a paired Bluetooth device or a card with NFC capabilities.

    Authenticators of this class are removable from, and can "roam" among, client devices.

    A platform authenticator is usually not removable from the client device. For example an Android smartphone or a Windows 10 computer with the associated security chips can act as an authenticator.

    Webauthn Server

    To set up a Webauthn server, you'll require components from two main categories:

    • : serivces required to load the data and convert into DTO or other objects

    • : services required to verify the input data and return the result of this validation

    Upon completion, you'll have the capability to and .

    Input Loading
    Input Validation
    register authenticators
    verify the identity of your users

    The Webauthn specification does not set any limit for the length of the icon.

    The icon may be ignored by browsers, especially if its length is greater than 128 bytes.

    User Entity Repository

    Whatever database you use (MySQL, pgSQL…), it is not necessary to create relationships between your users and the Credential Sources.

    Even if it is optional, we highly recommend setting the application ID

    Allowed: www.sub.domain.com, sub.domain.com, domain.com

    Not allowed:

    • www.sub.domain.com:1337, https://domain.com:443, sub.domain.com/index, https://user:password@www.domain.com.

    • 12.65.76.43 or [2001:db8:85a3:8d3:1319:8a2e:370:7348]

    The domain localhost can be used if the browser considers the context is safe (especially the IP address corresponds to a local address)

    How to determine the Relying Party ID?

    Discussion is ongoing for associating authenticators to multiple RP IDs such as my-company.com, meine-firma.de et ma-compagnie.fr

    Relying Party Icon

    The Webauthn specification does not set any limit for the length of the third argument.

    The icon may be ignored by browsers, especially if its length is greater than 128 bytes.

    https://m.my-app.com
    https://my-app.com
    https://www.my-app.com
    <form
        {{ stimulus_controller('@web-auth/webauthn-stimulus',
            {
                requestResultUrl: path('webauthn.controller.security.main.request.result'),
                requestOptionsUrl: path('webauthn.controller.security.main.request.options')
            }
        ) }}
    >
        <label for="username">Username</label>
        <input name="username" type="text" id="username" placeholder="Type your username here" autocomplete="username webauthn">
    
        <button
            type="submit"
            {{ stimulus_action('@web-auth/webauthn-stimulus', 'signin') }}
        >
            Sign in
        </button>
    </form>
    
    config/packages/webauthn.yaml
    webauthn:
        counter_checker: App\Service\CustomCounterChecker
    <?php
    
    use Webauthn\PublicKeyCredentialUserEntity;
    
    $userEntity = PublicKeyCredentialUserEntity::create(
        'john.doe',
        'ea4e7b55-d8d0-4c7e-bbfa-78ca96ec574c',
        'John Doe',
        'data:image/png;base64,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'
    );
    <?php
    
    use Webauthn\PublicKeyCredentialRpEntity;
    
    $rpEntity = PublicKeyCredentialRpEntity::create(
        'ACME Webauthn Server', // The application name
        'acme.com'              // The application ID = the domain
    );
    <?php
    
    use Webauthn\PublicKeyCredentialRpEntity;
    
    $rpEntity = PublicKeyCredentialRpEntity::create(
        'ACME Webauthn Server',
        'acme.com',
        'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAwFBMVEXm7NK41k3w8fDv7+q01Tyy0zqv0DeqyjOszDWnxjClxC6iwCu11z6y1DvA2WbY4rCAmSXO3JZDTxOiwC3q7tyryzTs7uSqyi6tzTCmxSukwi9aaxkWGga+3FLv8Ozh6MTT36MrMwywyVBziSC01TbT5ZW9z3Xi6Mq2y2Xu8Oioxy7f572qxzvI33Tb6KvR35ilwTmvykiwzzvV36/G2IPw8O++02+btyepyDKvzzifvSmw0TmtzTbw8PAAAADx8fEC59dUAAAA50lEQVQYV13RaXPCIBAG4FiVqlhyX5o23vfVqUq6mvD//1XZJY5T9xPzzLuwgKXKslQvZSG+6UXgCnFePtBE7e/ivXP/nRvUUl7UqNclvO3rpLqofPDAD8xiu2pOntjamqRy/RqZxs81oeVzwpCwfyA8A+8mLKFku9XfI0YnSKXnSYZ7ahSII+AwrqoMmEFKriAeVrqGM4O4Z+ADZIhjg3R6LtMpWuW0ERs5zunKVHdnnnMLNQqaUS0kyKkjE1aE98b8y9x9JYHH8aZXFMKO6JFMEvhucj3Wj0kY2D92HlHbE/9Vk77mD6srRZqmVEAZAAAAAElFTkSuQmCC'
    );
    <?php
    
    declare(strict_types=1);
    
    namespace Acme\Service;
    
    use Assert\Assertion;
    use Psr\Log\LoggerInterface;
    use Psr\Log\NullLogger;
    use Throwable;
    use Webauthn\PublicKeyCredentialSource;
    
    final class CustomCounterChecker implements CounterChecker
    {
        public function __construct(private UserRepository $userRepository)
        {
        }
    
        public function check(PublicKeyCredentialSource $publicKeyCredentialSource, int $currentCounter): void
        {
            if ($currentCounter > $publicKeyCredentialSource->getCounter()) {
                return;
            }
            
            $userId = $publicKeyCredentialSource->getUserHandle();
            $user = $this->userRepository->lockUserWithId($userId);
            $this->logger->error('The counter is invalid', [
                'current' => $currentCounter,
                'new' => $publicKeyCredentialSource->getCounter(),
            ]);
            throw new CustomSecurityException('Invalid counter. User is now locked.');
        }
    }
    Fingerprint reader on a laptop

    Roaming Authenticators

    Platform Authenticators

    USB device with fingerprint reader
    Webauthn compatible devices
    Android screenshot

    User Entity Repository

    Your application certainly has a user repository. Good news: there is no need to modify it!

    The User Entity Repository can be completely decoupled from the user entity used by your application.

    This repository should be declared as a Symfony service and shall implement Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface.

    Hereafter an example where the application User Repository is injected. This repository uses Doctrine and provides findOneBy* methods.

    By default, the User Entity Repository is not able to register any user account. You can add this behaviour by implementing the following interfaces:

    Webauthn\Bundle\Repository\CanRegisterUserEntity
  • Webauthn\Bundle\Repository\CanGenerateUserEntity

  • User Entity

    Registration Capability

    src/Repository/WebauthnUserEntityRepository.php
    <?php
    
    declare(strict_types=1);
    
    namespace App\Repository;
    
    use App\Entity\User;
    use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepositoryInterface;
    use Webauthn\PublicKeyCredentialUserEntity;
    
    final class WebauthnUserEntityRepository implements PublicKeyCredentialUserEntityRepositoryInterface
    {
        /**
         * The UserRepository $userRepository is the repository
         * that already exists in the application
         */
        public function __construct(private UserRepository $userRepository)
        {
        }
    
        public function findOneByUsername(string $username): ?PublicKeyCredentialUserEntity
        {
            /** @var User|null $user */
            $user = $this->userRepository->findOneBy([
                'username' => $username,
            ]);
    
            return $this->getUserEntity($user);
        }
    
        public function findOneByUserHandle(string $userHandle): ?PublicKeyCredentialUserEntity
        {
            /** @var User|null $user */
            $user = $this->userRepository->findOneBy([
                'id' => $userHandle,
            ]);
    
            return $this->getUserEntity($user);
        }
    
        /**
         * Converts a Symfony User (if any) into a Webauthn User Entity
         */
        private function getUserEntity(null|User $user): ?PublicKeyCredentialUserEntity
        {
            if ($user === null) {
                return null;
            }
    
            return new PublicKeyCredentialUserEntity(
                $user->getUsername(),
                $user->getUserIdentifier(),
                $user->getDisplayName(),
                null
            );
        }
    }
    

    Debugging

    If you have troubles during the development of your application or if you want to keep track of every critical/error messages in production, you can use a .

    User Verification

    Prove to me who you claim to be!

    User verification may be instigated through various authorization gesture modalities: a touch plus PIN code, password entry, or biometric recognition (presenting a fingerprint). The intent is to be able to distinguish individual users.

    Eligible authenticators are filtered and only capable of satisfying this requirement will interact with the user.

    Possible user verification values are:

    • required: this value indicates that the application requires user verification for the operation and will fail the operation if the response does not have the UV flag set.

    • preferred: this value indicates that the application prefers user verification for the operation if possible, but will not fail the operation if the response does not have the UV flag set.

    • discouraged: this value indicates that the application does not want user verification employed during the operation (e.g.,in the interest of minimizing disruption to the user interaction flow).

    Public constants are provided by AuthenticatorSelectionCriteria.

    • AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED

    • AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED

    • AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED

    config/packages/webauthn.yaml
    webauthn:
        logger: App\Service\MyPsr3Logger
    PSR-3 compatible logger

    Dealing with “localhost”

    If your are working on a development environment, https may not be available but the context could be considered as secured. You can bypass the scheme verification by passing the list of rpIds you consider secured.

    config/packages/security.yaml
    security:
        firewalls:
            main:
                webauthn:
                   secured_rp_ids:
                       - 'localhost'

    Secured Context

    Please be careful using this feature. It should NOT be used in production.

    Dealing with “localhost”

    If your are working on a development environment, https may not be available but the context could be considered as secured. You can bypass the scheme verification by passing the list of rpIds you consider secured.

    <?php
    
    declare(strict_types=1);
    
    use Webauthn\CeremonyStep\CeremonyStepManagerFactory;
    
    $csmFactory = new CeremonyStepManagerFactory();
    $csmFactory->setSecuredRelyingPartyId(['secure.localhost']);

    Secured Context

    Please be careful using this feature. It should NOT be used in production.

    Register Authenticators

    During this step, your application will send a challenge to the device. The device will resolve this challenge by adding information and digitally signing the data.

    The application will check the response from the device and get its credential ID. This ID will be used for further authentication requests.

    Creation Request

    To associate a device to a user, you need to instantiate a Webauthn\PublicKeyCredentialCreationOptions object.

    It will need:

    • The Relying Party

    • The User data

    • A challenge (random binary string)

    • A list of supported public key parameters i.e. an algorithm list (at least one)

    Optionally, you can customize the following parameters:

    • A timeout

    • A list of public key credential to exclude from the registration process

    • The Authenticator Selection Criteria

    • Attestation conveyance preference

    Let’s see an example of the PublicKeyCredentialCreationOptions object. The following example is a possible Public Key Creation page for a dummy user "@cypher-Angel-3000".

    The options object can be converted into JSON and sent to the authenticator .

    You can change the default values for each and all options

    The argument pubKeyCredParams contains a list of Webauthn\PublicKeyCredentialParameters objects that refer to COSE algorithms. The authenticators must use one of the algorithms in this list, respecting the order of preference on this list.

    An empty list corresponds to the default algorithms that are ES256 and RS256 (in this order). Those two algorithms are required by the specification.

    Please read detail .

    Please read detail .

    When the user already registered authenticators, you can pass a list of Webauthn\PublicKeyCredentialDescriptor objects as argument to avoid registering multiple times the same authenticator.

    What you receive must be a JSON object that looks like as follow:

    There are two steps to perform with this object:

    Now that all components are set, we can load the data we receive using the (variable $serializer).

    If no exception is thrown, you can go to the next step: the verification.

    Now we have a fully loaded Public Key Credential object, but we need now to make sure that:

    1. The authenticator response is of type AuthenticatorAttestationResponse

    2. This response is valid.

    The first step is easy to perform:

    The second step is the verification against

    • The Public Key Creation Options we created earlier,

    • The URI host

    The Authenticator Attestation Response Validator service (variable $authenticatorAttestationResponseValidator) will check everything for you: challenge, origin, attestation statement and much more.

    If no exception is thrown, the response is valid. You can store the Public Key Credential Source ($publicKeyCredentialSource).

    Extensions

    To avoid username enumeration, you should not set any excluded credential when the authenticator registration is performed for anonymous users. If this value is present, it indicates that the username exists and has registered credentials.

    On the contrary when adding new authenticators to already authenticated users, this value should be set.

    It is important to store the user entity and the options object (e.g. in the session) for the next step. The data will be needed to check the response from the device.

    Public Key Credential Parameters

    Customizing this list may lead to unexpected behavior. Please use with caution.

    Authenticator Selection

    Attestation

    Exclude Credentials

    Creation Response

    Data Loading

    Response Verification

    The way you store and associate these objects to the user is out of scope of this library. Please note that these objects implement \JsonSerializable and have a static method createFromJson(string $json). This will allow you to serialize the objects into JSON and easily go back to an object.

    using the API
    on this page
    on this page
    Load the data
    Verify it with the creation options set above
    Serializer
    <?php
    
    declare(strict_types=1);
    
    use Webauthn\PublicKeyCredentialCreationOptions;
    use Webauthn\PublicKeyCredentialRpEntity;
    use Webauthn\PublicKeyCredentialUserEntity;
    
    // RP Entity i.e. the application
    $rpEntity = PublicKeyCredentialRpEntity::create(
        'My Super Secured Application', //Name
        'foo.example.com',              //ID
        null                            //Icon
    );
    
    // User Entity
    $userEntity = PublicKeyCredentialUserEntity::create(
        '@cypher-Angel-3000',                   //Name
        '123e4567-e89b-12d3-a456-426655440000', //ID
        'Mighty Mike',                          //Display name
        null                                    //Icon
    );
    
    // Challenge
    $challenge = random_bytes(16);
    
    $publicKeyCredentialCreationOptions =
        PublicKeyCredentialCreationOptions::create(
            $rpEntity,
            $userEntity,
            $challenge
        )
    ;
    $jsonObject = json_encode($publicKeyCredentialCreationOptions);
    $publicKeyCredentialCreationOptions =
        PublicKeyCredentialCreationOptions::create(
            $rpEntity,
            $userEntity,
            $challenge,
            pubKeyCredParams: $publicKeyCredentialParametersList,
            authenticatorSelection: AuthenticatorSelectionCriteria::create(),
            attestation: PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
            excludeCredentials: $excludedPublicKeyDescriptors,
            timeout: 30_000,
        )
    ;
    use Cose\Algorithms;
    use Webauthn\PublicKeyCredentialParameters;
    
    $publicKeyCredentialParametersList = [
        PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES256K), // More interesting algorithm
        PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES256),  //      ||
        PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_RS256),  //      || 
        PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_PS256),  //      \/
        PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ED256),  // Less interesting algorithm
    ];
    {
        "id":"KVb8CnwDjpgAo[…]op61BTLaa0tczXvz4JrQ23usxVHA8QJZi3L9GZLsAtkcVvWObA",
        "type":"public-key",
        "rawId":"KVb8CnwDjpgAo[…]rQ23usxVHA8QJZi3L9GZLsAtkcVvWObA==",
        "response":{
            "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJQbk1hVjBVTS[…]1iUkdHLUc4Y3BDSdGUifQ==",
            "attestationObject":"o2NmbXRmcGFja2VkZ2F0dFN0bXSj[…]YcGhf"
        }
    }
    <?php
    
    declare(strict_types=1);
    
    use Webauthn\PublicKeyCredential;
    
    $data = '
    {
        "id":"KVb8CnwDjpgAo[…]op61BTLaa0tczXvz4JrQ23usxVHA8QJZi3L9GZLsAtkcVvWObA",
        "type":"public-key",
        "rawId":"KVb8CnwDjpgAo[…]rQ23usxVHA8QJZi3L9GZLsAtkcVvWObA==",
        "response":{
            "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJQbk1hVjBVTS[…]1iUkdHLUc4Y3BDSdGUifQ==",
            "attestationObject":"o2NmbXRmcGFja2VkZ2F0dFN0bXSj[…]YcGhf"
        }
    }';
    
    $publicKeyCredential = $serializer->deserialize(
        $data,
        PublicKeyCredential::class,
        'json'
    );
    <?php
    
    declare(strict_types=1);
    
    use Webauthn\AuthenticatorAttestationResponse;
    
    if (!$publicKeyCredential->response instanceof AuthenticatorAttestationResponse) {
        //e.g. process here with a redirection to the public key creation page. 
    }
    <?php
    
    declare(strict_types=1);
    
    $publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check(
        $authenticatorAttestationResponse,
        $publicKeyCredentialCreationOptions,
        'my-application.com'
    );

    Authenticator Algorithms

    The Webauthn data verification is based on cryptographic signatures and thus you need to provide cryptographic algorithms to perform those checks.

    The following algorithms are required in most situations:

    <?php
    
    declare(strict_types=1);
    
    use Cose\Algorithm\Manager;
    use Cose\Algorithm\Signature\ECDSA\ES256;
    use Cose\Algorithm\Signature\RSA\RS256;
    
    $algorithmManager = Manager::create()
        ->add(
            ES256::create(),
            RS256::create()
        )
    ;

    The order is important. By adding ES256 first, the relyaing party prefers an ES256 credential. Browsers are eager to satisfy preferences.

    The complete list of supported algorithms:

    The algorithm manager can be injected to your Ceremony Step Manager Factory.

    <?php
    
    declare(strict_types=1);
    
    use Webauthn\CeremonyStep\CeremonyStepManagerFactory;
    
    $csmFactory = new CeremonyStepManagerFactory();
    $csmFactory->setAlgorithmManager($algorithmManager);
    <?php
    
    declare(strict_types=1);
    
    use Cose\Algorithm\Manager;
    use Cose\Algorithm\Signature\ECDSA\ES256;
    use Cose\Algorithm\Signature\ECDSA\ES256K;
    use Cose\Algorithm\Signature\ECDSA\ES384;
    use Cose\Algorithm\Signature\ECDSA\ES512;
    use Cose\Algorithm\Signature\EdDSA\Ed256;
    use Cose\Algorithm\Signature\EdDSA\Ed512;
    use Cose\Algorithm\Signature\RSA\PS256;
    use Cose\Algorithm\Signature\RSA\PS384;
    use Cose\Algorithm\Signature\RSA\PS512;
    use Cose\Algorithm\Signature\RSA\RS256;
    use Cose\Algorithm\Signature\RSA\RS384;
    use Cose\Algorithm\Signature\RSA\RS512;
    
    $algorithmManager = Manager::create()
        ->add(
            ES256::create(),
            ES256K::create(),
            ES384::create(),
            ES512::create(),
    
            RS256::create(),
            RS384::create(),
            RS512::create(),
    
            PS256::create(),
            PS384::create(),
            PS512::create(),
    
            Ed256::create(),
            Ed512::create(),
        )
    ;

    Attestation and Metadata Statement

    Disclaimer: you should not ask for the Attestation Statement unless you are working on an application that requires a high level of trust (e.g. Banking/Financial Company, Government Agency...).

    With Symfony, you must enable this feature and define a Metadata Statement Repository in the configuration file.

    config/packages/webauthn.yaml
    webauthn:
        metadata_service:
            enabled: true
            repository: 'App\Repository\MyMetadataStatementRepository'

    The modification of these parameters is not recommended. You should try to sync your server clock first.

    Credential Creation Options

    By default, no Attestation Statement is asked to the Authenticators (type = none). To change this behavior, you just have to set the corresponding parameter in the Webauthn\PublicKeyCredentialCreationOptions object.

    There are 3 conveyance modes available using PHP constants provided by the class Webauthn\PublicKeyCredentialCreationOptions:

    • ATTESTATION_CONVEYANCE_PREFERENCE_NONE: the Relying Party is not interested in authenticator attestation (default)

    • ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT: the Relying Party prefers an attestation conveyance yielding verifiable attestation statements, but allows the client to decide how to obtain such attestation statements.

    • ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT: the Relying Party wants to receive the attestation statement as generated by the authenticator.

    • ATTESTATION_CONVEYANCE_PREFERENCE_ENTERPRISE: the Relying Party wants to receive uniquely identifying information from authenticators.

    Authentication without username

    With Webauthn, it is possible to authenticate a user without username. This behavior implies several constraints:

    1. During the registration of the authenticator, a Resident Key must have been asked,

    2. The user verification is required,

    3. The list of allowed authenticators must be empty

    In case of failure, you should continue with the standard authentication process i.e. by asking the username of the user.

    The bundle configuration should have a profile with the constraints listed above:

    config/packages/webauthn.yaml
    webauthn:
        credential_repository: '…'
        user_repository: '…'
        creation_profiles:
            default:
                rp:
                    name: 'My application'
                    id: 'example.com'
                authenticator_selection_criteria:
                    require_resident_key: true
                    user_verification: !php/const Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED
        request_profiles:
            default:
                rp_id: 'example.com'
                user_verification: !php/const Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED
    config/packages/webauthn.yaml
    webauthn:
        credential_repository: ...
        user_repository: ...
        creation_profiles:
            acme:
                attestation_conveyance: !php/const Webauthn\PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT
                rp:
                    name: 'My application'
                    id: 'example.com'

    Configuration References

    Configuration

    With Flex, you have a minimal configuration file installed through a Flex Recipe. You must set the repositories you have just created. You also have to modify the environment variables Relying_PARTY_ID and Relying_PARTY_NAME.

    You may also need to adjust other parameters.

    If you don’t use Flex, hereafter an example of configuration file:

    config/packages/webauthn.yaml
    webauthn:
    #    logger: null # PSR-3 compatible logging service
        credential_repository: 'Webauthn\Bundle\Repository\DummyPublicKeyCredentialSourceRepository' # CREATE YOUR REPOSITORY AND CHANGE THIS!
        user_repository: 'Webauthn\Bundle\Repository\DummyPublicKeyCredentialUserEntityRepository' # CREATE YOUR REPOSITORY AND CHANGE THIS!
        token_binding_support_handler: 'Webauthn\TokenBinding\IgnoreTokenBindingHandler' # We ignore the token binding instructions by default
        creation_profiles: # Authenticator registration profiles
            default: # Unique name of the profile
                rp: # Relying Party information
                    name: '%env(Relying_PARTY_NAME)%' # CHANGE THIS! or create the corresponding env variable
                    id: '%env(Relying_PARTY_ID)%' # Please adapt the env file with the correct relying party ID or set null
    #                icon: null # Secured image (data:// scheme)
    #            challenge_length: 32
    #            timeout: 60000
    #            authenticator_selection_criteria:
    #                attachment_mode: !php/const Webauthn\AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE
    #                require_resident_key: false
    #                user_verification: !php/const Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED
    #            extensions:
    #                loc: true
    #            public_key_credential_parameters: # You should not change this list
    #                - !php/const Cose\Algorithms::COSE_ALGORITHM_EdDSA #Order is important. Preferred algorithms go first
    #                - !php/const Cose\Algorithms::COSE_ALGORITHM_ES256
    #                - !php/const Cose\Algorithms::COSE_ALGORITHM_ES256K
    #                - !php/const Cose\Algorithms::COSE_ALGORITHM_ES384
    #                - !php/const Cose\Algorithms::COSE_ALGORITHM_ES512
    #                - !php/const Cose\Algorithms::COSE_ALGORITHM_RS256
    #                - !php/const Cose\Algorithms::COSE_ALGORITHM_RS384
    #                - !php/const Cose\Algorithms::COSE_ALGORITHM_RS512
    #                - !php/const Cose\Algorithms::COSE_ALGORITHM_PS256
    #                - !php/const Cose\Algorithms::COSE_ALGORITHM_PS384
    #                - !php/const Cose\Algorithms::COSE_ALGORITHM_PS512
    #            attestation_conveyance: !php/const Webauthn\PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE
        request_profiles: # Authentication profiles
            default: # Unique name of the profile
                rp_id: '%env(Relying_PARTY_ID)%' # Please adapt the env file with the correct relying party ID or set null
    #            challenge_length: 32
    #            timeout: 60000
    #            user_verification: !php/const Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED
    #            extensions:
    #                loc: true
    #    metadata:
    #        enabled: false
    #        mds_repository: 'App\Repository\MetadataStatementRepository'
    #        status_report_repository: 'App\Repository\StatusReportRepository'
    #        certificate_chain_checker: 'App\Security\CertificateChainChecker'

    Creation Profiles

    If you don't create the creation_profiles section, a default profile is set.

    Relying Party (rp)

    The relying Party corresponds to your application. Please refer to this page for more information.

    The parameter id is optional but highly recommended.

    Challenge Length

    By default, the length of the challenge is 32 bytes. You may need to select a smaller or higher length. This length can be configured for each profile:

    app/config/webauthn.yaml
    webauthn:
        creation_profiles:
            acme:
                rp:
                    name: 'ACME Webauthn Server'
                challenge_length: 16

    Timeout

    The default timeout is set to 60 seconds (60 000 milliseconds). You can change this value as follows:

    app/config/webauthn.yaml
    webauthn:
        creation_profiles:
            acme:
                rp:
                    name: 'ACME Webauthn Server'
                timeout: 30000

    For v4.0+, the timeout is set to null. The values recommended by the specification are as follow:

    • If the user verification is discouraged, timeout should be between 30 and 180 seconds

    Authenticator Selection Criteria

    This set of options allows you to select authenticators depending on their capabilities. The values are described in of the protocol.

    This option indicates the algorithms allowed for your application. By default, a large list of algorithms is defined, but you can add custom algorithms or reduce the list.

    If you need the , you can specify the preference regarding attestation conveyance during credential generation.

    You can set as many extensions as you want in the profile. Please also for more information.

    The parameters for the request profiles (i.e. the authentication) are very similar to the creation profiles. The only difference is that you don’t need all the detail of the Relying Party, but only its ID (i.e. its domain).

    Please note that all parameters are optional. The following configuration is perfectly valid. However, and as mentioned above, the parameter id is highly recommended.

    If the user verification is preferred or required, the range is 300 to 600 seconds (5 to 10 minutes)

    These behaviors are not necessarily followed by the web browsers.

    app/config/webauthn.yaml
    webauthn:
        creation_profiles:
            acme:
                rp:
                    name: 'ACME Webauthn Server'
                authenticator_selection_criteria:
                    attachment_mode: !php/const Webauthn\AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM
                    require_resident_key: true
                    user_verification: !php/const Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED
    app/config/webauthn.yaml
    webauthn:
        creation_profiles:
            acme:
                rp:
                    name: 'ACME Webauthn Server'
                public_key_credential_parameters:
                    - !php/const Cose\Algorithms::COSE_ALGORITHM_ES256
                    - !php/const Cose\Algorithms::COSE_ALGORITHM_RS256
    app/config/webauthn.yaml
    webauthn:
        creation_profiles:
            acme:
                rp:
                    name: 'ACME Webauthn Server'
                attestation_conveyance: !php/const Webauthn\PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT
    app/config/webauthn.yaml
    webauthn:
        creation_profiles:
            acme:
                rp:
                    name: 'ACME Webauthn Server'
                extensions:
                    loc: true
                    txAuthSimple: 'Please add your new authenticator'
    app/config/webauthn.yaml
    webauthn:
        request_profiles:
            acme:
                rp_id: 'example.com'
    app/config/webauthn.yaml
    webauthn:
        request_profiles:
            acme: ~

    Public Key Credential Parameters

    The order is important. Preferred algorithms go first.

    It is not recommended changing the default list unless you exactly know what you are doing.

    Attestation Conveyance

    The use of Attestation Statements is generally not recommended unless you REALLY need this information.

    Extensions

    The example below is totally fictive. Some extensions are but the support depends on the authenticators, on the browsers and on the relying parties (your applications).

    Request Profiles

    If you don't create the creation_profiles section, a default profile is set.

    the advanced concepts
    attestation of the authenticator
    refer to this page
    defined in the specification

    Register Additional Authenticators

    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…).

    With a Symfony application, the fastest way for a user to register additional authenticators is to use the “controller” feature.

    To add a new authenticator to a user, the bundle needs to know to whom it should be added. This can be:

    • The current user itself e.g. from its own account

    • An administrator acting for another user from a dashboard

    For that purpose, a User Entity Guesser service should be created. This service shall implement the interface Webauthn\Bundle\Security\Guesser\UserEntityGuesser and its unique method findUserEntity.

    You can directly use the Webauthn\Bundle\Security\Guesser\CurrentUserEntityGuesser as a Symfony service. It is designed to identify the user that is currently logged in.

    In the example herafter where the current user is guessed using a controller parameter. This can be used when an administrator is adding an authenticator to another user account.

    Now you just have to enable the feature and set the routes to your options and response controllers.

    Now you can send requests to these new endpoints. For example, if you are using the Javascript library, the calls will look like as follow:

    The default is used. You can change it using the dedicated option.

    You can customize the responses returned by the controllers by using custom handlers. This could be useful when you want to return additional information to your application.

    There are 3 types of responses and handlers:

    • Creation options,

    • Success,

    • Failure.

    This handler is called during the registration of a authenticator and has to implement the interface Webauthn\Bundle\Security\Handler\CreationOptionsHandler.

    This handler is called when a client sends a valid assertion from the authenticator. This handler shall implement the interface Webauthn\Bundle\Security\Handler\SuccessHandler. The default handler is Webauthn\Bundle\Service\DefaultSuccessHandler.

    This handler is called when an error occurred during the process. This handler shall implement the interface Webauthn\Bundle\Security\Handler\SuccessHandler. The default handler is Webauthn\Bundle\Service\DefaultFailureHandler.

    In the case the current user s supposed to be administrator, the user entity can be determined using the query parameters and a route like /admin/add-authenticator/for/{user_id}.

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

    The option hide_existing_credentials should be false when adding authenticators from a secured area. Set true when this action is performed by anonymous users.

    Creation Profile

    Response Handlers

    Creation Options Handler

    Success Handler

    Failure Handler

    creation profile
    App\Guesser\FromQueryParameterGuesser.php
    <?php
    
    declare(strict_types=1);
    
    namespace App\Guesser;
    
    use Assert\Assertion;
    use Symfony\Component\HttpFoundation\Request;
    use Webauthn\Bundle\Repository\PublicKeyCredentialUserEntityRepository;
    use Webauthn\Bundle\Security\Guesser\UserEntityGuesser;
    use Webauthn\PublicKeyCredentialUserEntity;
    
    final class FromQueryParameterGuesser implements UserEntityGuesser
    {
        public function __construct(
            private PublicKeyCredentialUserEntityRepository $userEntityRepository
        ) {
        }
    
        public function findUserEntity(Request $request): PublicKeyCredentialUserEntity
        {
            $userHandle = $request->query->get('user_id');
            Assertion::string($userHandle, 'User entity not found. Invalid user ID');
            $user = $this->userEntityRepository->findOneByUserHandle($userHandle);
            Assertion::isInstanceOf($user, PublicKeyCredentialUserEntity::class, 'User entity not found.');
    
            return $user;
        }
    }
    config/packages/webauthn.yaml
    webauthn:
        controllers:
            enabled: true # We enable the feature
            creation:
                from_user_account: # Endpoints accessible by the user itself
                    options_path: '/profile/security/devices/add/options' # Path to the creation options controller
                    result_path: '/profile/security/devices/add' # Path to the response controller
                    user_entity_guesser: Webauthn\Bundle\Security\Guesser\CurrentUserEntityGuesser # See above
                    hide_existing_credentials: false
                from_admin_dashboard: # Endpoint accessible by an administrator
                    options_path: '/admin/security/user/{user_id}/devices/add/options' # Path to the creation options controller
                    result_path: '/admin/security/user/{user_id}/devices/add' # Path to the response controller
                    user_entity_guesser: App\Guesser\FromQueryParameterGuesser # From the example
                    hide_existing_credentials: false
    config/packages/security.yaml
    security:
        access_control:
            - { path: ^/profile,  roles: IS_AUTHENTICATED_FULLY } # We protect all the /profile path
            - { path: ^/admin,  roles: ROLE_ADMIN }
    // Import the registration hook
    import {useRegistration} from 'webauthn-helper';
    
    // Create your register function.
    // By default the urls are "/register" and "/register/options"
    // but you can change those urls if needed.
    const register = useRegistration({
        actionUrl: '/profile/security/devices/add',
        optionsUrl: '/profile/security/devices/add/options'
    });
    
    
    // We can call this register function whenever we need
    //   No "username" or "displayName" parameters are needed
    //   as the user entity is guessed by the dedicated service
    register({})
        .then((response) => console.log('Registration success'))
        .catch((error) => console.log('Registration failure'))
    ;
    config/packages/webauthn.yaml
    webauthn:
        controllers:
            enabled: true
            creation:
                from_user_account:
                    …
                    profile: custom_profile
    config/packages/webauthn.yaml
    webauthn:
        controllers:
            enabled: true
            creation:
                from_user_account:
                    …
                    options_handler: … # Your handler here
    config/packages/webauthn.yaml
    webauthn:
        controllers:
            enabled: true
            creation:
                from_user_account:
                    …
                    success_handler: … # Your handler here
    config/packages/webauthn.yaml
    webauthn:
        controllers:
            enabled: true
            creation:
                from_user_account:
                    …
                    failure_handler: … # Your handler here

    Installation

    The allows adding high interaction application from Twig templates without a single line of JS.

    With the version 4.1, a Stimulus Controller is provided. This component turns a basic login form into a Webauthn compatible one.

    To use this nice feature, you first need to install and configure Symfony UX. Please refer to the .

    Then, install the package using composer:

    It is now time to create your first form.

    composer require web-auth/webauthn-stimulus
    Symfony UX Initiative
    official documentation

    Authenticator Selection Criteria

    By default, any type of authenticator can be used by your users and interact with you application. In certain circumstances, you may need to select specific authenticators e.g. when user verification is required.

    The Webauthn API and this library allow you to define a set of options to disallow the registration of authenticators that do not fulfill with the conditions.

    The class Webauthn\AuthenticatorSelectionCriteria is designed for this purpose. It is used when generating the Webauthn\PublicKeyCredentialCreationOptions object.

    Available Criteria

    Authenticator Attachment Modality

    You can indicate if the authenticator must be attached to the client (platform authenticator i.e. it is usually not removable from the client device) or must be detached (roaming authenticator).

    Possible values are:

    • AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE: there is no requirement (default value),

    • AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM: the authenticator must be attached,

    • AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM: must be a roaming authenticator.

    A primary use case for platform authenticators is to register a particular client device as a "trusted device" for future authentication. This gives the user the convenience benefit of not needing a roaming authenticator, e.g., the user will not have to dig around in their pocket for their key fob or phone.

    With this criterion, a Public Key Credential Source will be stored in the authenticator, client or client device. Such storage requires an authenticator capable to store such a resident credential.

    .

    With this example, with require the user verification (PIN, fingerprint...), a resident key and an authenticator embedded onto a device. This is typacally what you will require for Windows Hello or Face ID authentication.

    use Webauthn\AuthenticatorSelectionCriteria;
    use Webauthn\PublicKeyCredentialCreationOptions;
    
    $authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create(
        userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED,
        residentKey:AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_REQUIRED,
        authenticatorAttachment: AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM
    );
    
    $publicKeyCredentialCreationOptions =
        PublicKeyCredentialCreationOptions::create(
            $rpEntity,
            $userEntity,
            $challenge,
            authenticatorSelection: $authenticatorSelectionCriteria
    );

    Resident Key

    A resident key shall be created you want to .

    User Verification

    Example

    Please refer to this page
    authenticate users without username