Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
With Webauthn, it is possible to authenticate a user without username. This behavior implies several constraints:
During the registration of the authenticator, a Resident Key must have been asked,
The user verification is required,
The list of allowed authenticators must be empty
The bundle configuration should have a profile with the constraints listed above:
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_REQUIREDThe easiest way to manage the user verification requirements in your Symfony application is by using the creation and request profiles.
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_PREFERREDDisclaimer: 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.
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.
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.
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'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.
Please be careful using this feature. It should NOT be used in production.
security:
firewalls:
main:
webauthn:
secured_rp_ids:
- 'localhost'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.
<?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;
}
}Now you just have to enable the feature and set the routes to your options and response controllers.
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
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 exampleAs the user shall be authenticated to register a new authenticator, you should protect these routes in the security.yaml file.
security:
access_control:
- { path: ^/profile, roles: IS_AUTHENTICATED_FULLY } # We protect all the /profile path
- { path: ^/admin, roles: ROLE_ADMIN }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:
// 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'))
;The default creation profile is used. You can change it using the dedicated option.
webauthn:
controllers:
enabled: true
creation:
from_user_account:
…
profile: custom_profileYou 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.
webauthn:
controllers:
enabled: true
creation:
from_user_account:
…
options_handler: … # Your handler hereThis 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.
webauthn:
controllers:
enabled: true
creation:
from_user_account:
…
success_handler: … # Your handler hereThis 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.
webauthn:
controllers:
enabled: true
creation:
from_user_account:
…
failure_handler: … # Your handler hereAn 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.
In the previous example, we asked for the location of the device and we expect to receive geolocation data in the extension output.
<?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 easiest way to manage that is by using the creation and request profiles.
webauthn:
…
creation_profiles:
default:
rp:
name: 'My Application'
id: 'example.com'
extensions:
uvm: true
request_profiles:
default:
rp_id: 'example.com'
extensions:
uvm: trueIf 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 .
webauthn:
logger: App\Service\MyPsr3LoggerBy 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.
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 authenticate users without username.
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_PREFERREDThe 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.
webauthn:
counter_checker: App\Service\CustomCounterCheckerThe following example is fictive and show how to lock a user, log the error and throw an exception.
<?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.');
}
}