Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Seamless Integration with PHP Applications: Our Webauthn Documentation and Tutorials
Step-by-step guide for migrating from v3.x to v4.0
How to install the library or the Symfony bundle?
What is an authenticator?
Adoption by web browsers
Prove to me who you claim to be!
composer require web-auth/webauthn-lib$publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check(
$authenticatorAttestationResponse,
$publicKeyCredentialCreationOptions,
'my-application.com',
['localhost']
);$publicKeyCredentialSource = $authenticatorAssertionResponse->check(
$publicKeyCredentialSource,
$authenticatorAssertionResponse,
$publicKeyCredentialRequestOptions,
'my-application.com',
$userHandle,
['localhost']
);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_PREFERREDwebauthn:
token_binding_support_handler: Webauthn\TokenBinding\TokenBindingNotSupportedHandlerwebauthn:
logger: App\Service\MyPsr3Loggercomposer require web-auth/webauthn-stimulussecurity:
firewalls:
main:
webauthn:
secured_rp_ids:
- 'localhost'use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialCreationOptions;
$publicKeyCredentialCreationOptions =
PublicKeyCredentialCreationOptions::create(
$rpEntity,
$userEntity,
$challenge,
authenticatorSelection: AuthenticatorSelectionCriteria::create(AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED)
)
;// Public Key Credential Request Options
use Webauthn\PublicKeyCredentialRequestOptions;
$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create(
random_bytes(32),
userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_DISCOURAGED
);composer require web-auth/webauthn-symfony-bundle web-auth/webauthn-stimuluscomposer config --json extra.symfony.endpoint '["https://api.github.com/repos/web-auth/recipes/contents/index.json?ref=main", "flex://defaults"]'webauthn:
metadata: ## Optional
enabled: true
mds_repository: 'App\MDS\MetadataStatementRepository'
status_report_repository: 'App\MDS\MetadataStatementRepository'
#certificate_chain_checker: 'Webauthn\CertificateChainChecker\PhpCertificateChainChecker::class'You have just found a bug?
Registration and Authentication process overview
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
);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<?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;
}
}
}<?php
declare(strict_types=1);
use Webauthn\AuthenticationExtensions\AuthenticationExtension;
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
use Webauthn\PublicKeyCredentialRequestOptions;
// Extensions
$extensions = AuthenticationExtensionsClientInputs::create([
AuthenticationExtension::create('loc', true)
]);
// Public Key Credential Request Options
$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create(
random_bytes(32), // Challenge
extensions: $extensions
);webauthn:
…
creation_profiles:
default:
rp:
name: 'My Application'
id: 'example.com'
extensions:
uvm: true
request_profiles:
default:
rp_id: 'example.com'
extensions:
uvm: truewebauthn:
counter_checker: App\Service\CustomCounterChecker<?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.');
}
}Webauthn\PublicKeyCredentialCreationOptionsform<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><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>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
);// Public Key Credential Request Options
use Webauthn\PublicKeyCredentialRequestOptions;
$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create(
random_bytes(32),
userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED
);$authenticatorAssertionResponseValidator =
AuthenticatorAssertionResponseValidator::create(
null, //Deprecated Public Key Credential Source Repository. Please set null.
null, //Deprecated Token Binding Handler. Please set null.
$extensionOutputCheckerHandler,
$coseAlgorithmManager
)
->setCounterChecker(new CustomCounterChecker())
;<?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('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\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
}
}composer require web-auth/webauthn-symfony-bundle<?php
return [
//...
Webauthn\Bundle\WebauthnBundle::class => ['all' => true],
];<?php
declare(strict_types=1);
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return function (RoutingConfigurator $routes) {
$routes->import('.', 'webauthn');
};webauthn:
metadata_service:
enabled: true
repository: 'App\Repository\MyMetadataStatementRepository'webauthn:
android_safetynet:
http_client: 'my.psr18.http.client'
request_factory: 'my.psr17.request_factory'webauthn:
android_safetynet:
max_age: 60000 # in milliseconds. Default set to 60000 = 1 min
leeway: 2000 # in milliseconds. Default set to 0webauthn:
credential_repository: ...
user_repository: ...
creation_profiles:
acme:
attestation_conveyance: !php/const Webauthn\PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT
rp:
name: 'My application'
id: 'example.com'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<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>
Overview of the framework
aka the application you are interacting with
<?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
);
}
}
<?php
use Webauthn\PublicKeyCredentialRpEntity;
$rpEntity = PublicKeyCredentialRpEntity::create(
'ACME Webauthn Server' // The application name
);<?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'
);


It's all about users



ATTESTATION_CONVEYANCE_PREFERENCE_DIRECTrp Entity, the User Entity may have an icon. This icon must also be secured.<?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\AndroidSafetyNetAttestationStatementSupport;
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());
$androidSafetyNetAttestationStatementSupport = AndroidSafetyNetAttestationStatementSupport::create()
->enableApiVerification( $psr18Client, $googleApiKey, $psr17RequestFactory) // Optional
;
$attestationStatementSupportManager->add($androidSafetyNetAttestationStatementSupport);
$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));$attestationObjectLoader = AttestationObjectLoader::create($attestationStatementSupportManager);<?php
use Webauthn\PublicKeyCredentialCreationOptions;
$publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create(
$relyingParty,
$userEntity,
$challenge,
attestation: PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT,
);<?php
use Webauthn\PublicKeyCredentialUserEntity;
$userEntity = PublicKeyCredentialUserEntity::create(
'john.doe', // Username
'ea4e7b55-d8d0-4c7e-bbfa-78ca96ec574c', // ID
'John Doe' // Display name
);<?php
use Webauthn\PublicKeyCredentialUserEntity;
$userEntity = PublicKeyCredentialUserEntity::create(
'john.doe',
'ea4e7b55-d8d0-4c7e-bbfa-78ca96ec574c',
'John Doe',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4wkIDCoJiw0E+gAAFMhJREFUeNrtm3mcHdV157/3Vr16S7+l90W9aEFrswShBdngAHYggmBPogTbMeCEkNjxhMVm4jjOeGwImXGwMzi28GArGcOAHOeD8cYiid1YyFIasUlCoqWWGvWu3re3Vb26J3+87pZa6m51SyJOPpnzUX9aH1Xdc8/v3FtnF/x/+s9N6r3eoG7eQlo6mllWW+cElJQjshiRJSDzgdIxGUaBNpRqFKX3+WJ1loXw2twwzUcP/MdUwOJ51VQ6hgHfLlfi/56IXKtgOcg8IDzN3i6oVuBNlP6xUdaW+cXxwQNdPbx77Nh/HAWsqK1DtK0tk71OGfMlkFWAPUc2WVANovRG0fbPFOK+3dJyzmW1zjXD+toaUNqyTfZWJWYjsBTQZ8DKBuYr5FqFKROhoSwWTVeUVdE90P/vUwHLamuJeCOIsj6OyN8DxeeArQOsUVAm2n5JfM/tGR4+ZzKfyclMSZve7aGjtRUTr7gIkbuBwrmsF8AYQUSmkVP+QIt/pxerVvV1C/79KOB/NIzAVmFvd2H5J17quzU877xHEH/pJHAiTI0LjAi+MYQcmyvXLON9F583tuaUV21l2bet+Mjvf/ra55vLeU3ye58lnbER/NR3n0LVX4WVy0YJxz5sbPuuvobnf+3gN/4s4CeHEBRGDFopiuIF5IxheCSdB4eAgOPYlBZGWb6wkvWX1XPJijqyrsemH+1g2yv7cD0frY+LKMZQcc2N3uLPfO0tW6n7SQ0/6dvBUdn/Eps+ff2/nQLuecNlkZNWu4ZDK31l/5VR+rd8Lxs6+L//lP6dTyHKIh4NsXR+Bavq53P+eVVsfrqBju5B4tEQsYIQlaUJVi6voX5RFWVFUYIBGyMGUGSyHlt37OeHz75GS2fe4GmtQAzB8vnU3/3PhOctymhjnrYk97/WxTNvHHHD8pWVznuvgM/9Sxblu6G0Ct5otP1lg6pTGob3v8qB//lJTHKQtRct4uPrV3P+okrCoQDGCH1DSUSEcNDBCVgEbAtLa0QEc9J9VyovWmvXAE+8vJdde47Q2tVPLuejlWLBrfcy7yN/ghjQSIs2ub8OS/b7YjmZb1wafO8U8JlXkliYIj8Q/ksffbugwuNcjj58L+0/3sj1V13Mf/3or5OIhjHGMA5NqfGtprcHJ5NWCiNC72CSnXua2fbK2+w72ELJ5b/DkrseRGk9tr2kLcxGy0v/rY8eePDygnOrgCeaBtlyLISjpcLVzn0++mZOMKDGTbP/3puoHD7IVz+7gYqSGMbMEuVshFQKrRR9Q0m+9f0X2NkT5Px7HsOOFcLxbYyFedQx7hdco45dV5HhI4sLT6/k2QjwTE8ER0ulq4MbffQnJ61T4A5043Yf5forLqKyJH5OwUPei/jGUFJYwEd/czXhbD/Zvk7U5OPTPvqTrg5udLRUPtMTmRXv0yrg9p0ZAviFrnLu81E3cNKtURoy3e0kLI9LVtTlLfx7RMY3zK8qobowSKa3c6r7q3zUDa5y7gvgF96+M3N2CvjsziSOyTpZ7XzBV/oT073nDvWyqDJOVVli0ulrrfI/Kv9bqdN/cUpxfM3EuvwzAUJBm7J4iFxyaFoevtKfyGrnC47JOp/dmZxxv2kTlC+97vHkMZvLiv2P+ajbZnpXfJ+6yiLCwcCYgRMybo7mzj5auwZwcz7F8QhLassoTUQROCXiy3/nMJLK0tTWQ2dfPtytLI6zuKaUeEGI/JELQcfmNJbU9lG3pe2C/Tv6rUe/9LrH31wSmL0C/m5vmiMpxRUl/oVZ0V8RVHT6IwOnuAwrGOHto70c7hyks3+UpvY+DrX2knY9ABxbU1Ma40OrFvP+82spT0SwxoIc3wj9I2n2NnezreEgh9r6SLs+IhCwNfMri7hgUSXVJTEWVibwLQenqGxGEy6oqI/+yhUl/pv9ntr7d3vT/PmF4anEP5Vu3z6ERiJZJ/aQj/7oTKrO9h+j7dnNJH/5Q3I5n5G0i6UVsbBDOGhPuLKs6zOUymIEastirFs+j7XLqgBoaOxk96EuWnuGESPEIkFCjjWxNu3mGEm5+EaIhR201sQuv4Gaa24mWFwxk3hYmMeC7sgtBpXa+IHE6RXw1YY+3vGLiVn+Da7ohwU1rTkVERofuofOnz8OWhOwNFevrOMDF9RQUxonErTRY8HOaNqlrXeEl/e28vybreR8gxPIJ6Ou52Nbmt+4uJYrLqylpjRGNOyglMIYQyrr0dY7wi/2tvH8my14vgFjqLry91h2y1dmtC0KSTnK/OGIb/1wudXPF9eWzPwJtOfCJEiXZCV050zg84t9aoJpsrEgQ+kcl9VX87kNaymMhlBAKuPRP5xEK8WiqiLq55ezakklI2mPVw92EQ/nbcawCO9bXsV/27CW4niEjOuNRY6GskQBkVCA5XVlrFpSxWgmx853OkgUBKgJprHx8WeotQgq4om+MyHpF9tz4b5TMZxAd2w7SsoJEzTmOhG1ZibwAhSQ4ZYP1uKefxUPPPUWa5ZVURwL4xuho3eIZxsOMDCSAqCsMMo1a1dQWRLnksWVRII2N3/wfIwIm1/cz0WLyilNROjsG+bZhgP0Do4iQFEswjVrV1BVmqAkHmHVkkqMCJ9efwFWaS2PkWGI6IwRnaDW+HbwuqzWj96x7SjfWj9/agXoWDGxzHAkZUdvFMVpMwsthrJogOJIBTVlcUJOnp0xhu6BES67cBFlRTFEhK7+YTr7hqkojlEQClBdGueCheUA1OxtI+TYGIGuvmFWL59PRUkMhaJ7YIRj/SNUFMewtEVBKEB5YQH1C8rpVwG0mNPGswJOznBjzB3+kcSKU9PeADdQgK1klRh96enATzAf80aJiMPgaBaRvEu78Lx5+WRn7L3CWCSfGwh0DSSJR5wJVxhybAZHs4Bw/qIqtD4enhRGw/jG5O2BCJ39SUrjYcYy6lmTKH2pH4yuyonaPukQx/9yx5ONbF6j8AwbZI7VHKXg4kXl7GnuZiiZwRoLeozIWDEkL2rAtni7pZc9zd2sWVo5obz6uhL2vdvDwGgG29ITa8YzRa01tqV5p7WPNw4f4/0r5s1FvPFbUOgZNmxeo7jjycZTFWCVz+ePGzIJg5r16Rs0OSyMES5ZXEE8HGDT1rc42j1MzjcolVeOEaFvOM3W3Uf41s9e49cvqGFpdTGCYARWnldOaTzEpi1v0XxsaNJaEWFgJM2zrzfz9z/ZzftXVLG8tgQRye89h6KWQV36xw2ZhFV+3AZMfD23vWrQyNqM0dsEimbDUGPYkHqWS903EWXR2T/KI8/vo6lzkJJ4hERBEIUilfXoHkyiFaxfvZDL62uwrEn5FL3DKR5/5SCH2gcojkcojAZRSpHOeBwbTKIU/OYlC1i/ehHBgI3G51+ci/lx5JpZK0HBQEib9QbV8MAaPbE3mw8M8cJQnIiW211R35rDtaLUDHBNZgdL3cNElEsmm+NQez9NHQMMJDOIEQpCAWrL4iyrKaY4HqF/OEX/cGri01BKURyPEA0HOdzRz6GOAQZGM/jja0tjLKspobSwIO9ecTjonMezocvo1UVzKmo4Su5IGbXxQ4lhblqRyK+95aGXuXDTdarxG0P/1yjrljnwQwALw7Xpl7ki+yqG8aTnpFgfNZEDjAdG6awLQDjoEA076PF7P8NajfBycA1bw1fgo+dc0tLiP7Tsc4lb935qizx0yxV5LxCqWUrT3Y0BEarmylEBOTQt1jxyaDRTl7ZPTJO1UsQiQWKR4CQ++Txq5rWT95o7iVDVdHdjIKQtF8bcYKy4DIHIsG+VzbZcdbIS2u0KhnScYjOIzKBFL+cjIjiBydFb1suhlCJgWzPsIwzpBO12xRmXs5VllRWUVkYUuDDmBXwBI8SQM+vkKGBQx2m1Kscu69SkteJQazdbdr6NbwyW1lha4xvDlp1vc6i1e1IZfCoFtFqVDOr4mdfzhWIjxPwxMW0Ak2dXAMyujjQF5dC84dSzPHcER7yp9xYoThTQtnuQx55/ndqKvLNpPTZA31CS912wcMY0P6OCvOHUn/H1H6OIQU1UTW2AlK8BtDmLTpEGDtkLaLQX8mveO1O6JhGhqiTOx69exf7mLjp781Wd6rJCrl67nLKi6HStMTSGRnshh+wFZ9XOMqBT/vFQ0wbQSsYEVGdV0fOUzUuhdVT7xyiZxhaIQFlRjCuLj1eOtVaIMC14hdCri3gptA5P2WfV01cn4M0rFghrQ1gbX4F/FrzzxtCq4JnQB0ir0LT2QEQm1Q5naIqiENIqxDOhD9BunbnxO0FGP6yNH9bmuALGaHTs52w3YI+zjKfDV5JUEfRZ3CmNkFQRng5fyR5n2bma5piE0wYIKBAYQdGHsPhsdxA0rzoXMqyiXJt5mSq/B5j956XGuHRYZWwNXUFjYCHnbJhF0WcrRsa52QBDXa0o30ubsoU96LlOsky/0zvOIroDZVyWfZ3V6TcI4c1qZZoAu8Mr2RG8hH4VQ53DVoPxcz0jXc1psfJVYg0gIz089uElnqV12znAjdIgXpZc/zHajrbx+Ds5GntcRlNpsl4O3zcT3/24PfB9Q9bLMZpK09jj8qPGHG1H28j1H0O8LEpzTi6BpXXbYx9e4slIDzB2A/775VX4DWArtdvke5dntpUCkxph9ODrDO/fSaa3Az+dxFE5MldWkrIcTNqbaHyMFzPH835jBK0gM5ik4+eP4IqNFS4gVDqPeP37iC69BB2Jza0SMlk8sZXa/TsNeczfHVdAXU0ttzfk0IoGJVa3QMWZcM92NtP9wg9ItjRi/HxoK4By8vMCiWiQdDaHbwy+MYgZzwbB0hrH1oSDNvFsPsnxvSzGy+IO9TH67gEK9u2g/EO/T7Bq4RkpQUG3pUxDeAzzxA0AcPwMiBzGiuwHPTcFKPBHBul65hGSrY0obU20rsfJ0ppIyCHoBPKVIiMTSY5CocZuhaUVlh6zFWM3RCmFiGGkeR/yzCNU/+6dWJM7w7Mks9/20odP7KpOSKkHOjgSiaYs2D5XtkpBsul1Uu1NKD19MjPu6rVSWJbGtixsy8KydD4VZuaOl9IWyfYmkk2vM4s24ylkwfYjkWhKD3ScqoCvX7eUatdgK/mJQrpmDV6DnxxmpGkPYsypzwEj4BlmJbRS+XfNNIZIjGG0aQ9+ciRvGGcrJ9JlK/lJtWv4+nXHZ7gm+TwrM4wjuf1uqHj7WCt8Jo7gZRnav4vuXVtJdRwh4Ew9npLNGdoGslxUPbvJjdaBLNmcmfKZ0prefb8k1d9F+bpridevg0DwtJ+DRrYH0wP7XWWf9O8n0M0LDIOhEtdWPKxmiAqVglxfJ91bv0frE99l8PA+cm4W408dSftG2H10ZFpQJyvrtaMj+NMMWRjfJ+dmGTy8j9Ynvkv31u+RO3VY4uSzGrUVDw+GStybF0yWYZICVteVEPKSWF7yFxp5eTpumdZG2n/6bfreegWlNLYTRETwvOyUMb3Wij1tI2xvGp741qc8JaXY3jTMnraRKesCJ+5hO0GU0vS99QrtP/026ZbGaZ23Rl62vOQvQl6S1XUlJz07iT5WmySpwqOW5O5XyOShXAXpowdof+ofSHY0g9YopXCCISw7gO95eG6+wXHSMlKuz6O7Onnp4BC+AeuEIQhLK3wDLx0c5NFdnaRcf6o8Es/N4nselh3ACYbycYTWJDuaaX/qH0gfPXDKTVBIvyW5+5MqPPqx2lOHJabU2W0/78Pxs4F0tOLrOfSd429m25tof+I7ZHo7T3FzIoLvuXiei2XZBJwgMhbsTMyHCUQci1UL4qxZEKe2KG8zWgeyvPruMK+9O5wHf8JEiIigZAy8nyMQcLACzikdYTGGcOk8qv/Ln+LMO2/iDGzMN8Ojxz7vWkHvgStLTsE67X28bZeLpajLYP/YKLXKH+6n46ffZuToO6eAP5GM8cm5LuFQkMULF3C0o4vMWPV3HJQxQsDShJ28y0y7Pp5v8uMwJ/AKBR0WVFdy6Mi7pDNZbMdBz+RmjSE2fznzfvvPsOLFaJHXQuQ2+ELLA+umbnVOi6Q8KAwau8XGfFH5fs/g7mcYbWmcETyA1haBYIhEopBP33wDH7v+asT4Ey5Skb/+RoTRbI7RbA4jkm+nnQBEjM9Hr7+aT910A4lEIYFgaEbwkPcQoy2NDO5+BuX7PTbmi4PGbikPzlCnnO7Bl1cGKZFRLl9rveAefu2ewb07UsyBtGVRVlrCimVLsO0AguSBiZlIgtSYQo73Ak3+HQTbDlC/bAllpSVoa25T/YN7d6Tcw6/dc/la64USGeXLK6efHp0x973//XHW3/QZEwwG/9F42Sq0/gtEAsyS8mUu0FrPeHrHv2c1/mdssuR0s1BTaV57xst+Y+ClH/zj/9v2sNm2+cGZXz8dv22bHyTl5rKhguhXLdv+JkrNLqn/VZBSnmXb3wwVRL+acnPZ04GflQIAnnv0AdxMJhmKJu62neDXlFLpXzXWU7GrtO0EvxaKJu52M5nkc48+MKt1s46mX/zBdzDGT4bjhffawfDnldbdv2rQE+C17raD4c+H44X3GuMnX/zBd2a9dk4l9uce2YjSVraoZuGDTiR2k7btBtS5LFjNFbkSbdsNTiR2U1HNwgeVtrLPPbJxTizm3GPYsuk+KqtLTXJo6LlQLLHBsgP3K617/s2xa91j2YH7Q7HEhuTQ0HOV1aVmy6b75sznjCqg/+euPwFg/R/d1R4pKv2Cmxz5Wc7N3Gl8/2oxJn6uQJ5YNjsB+LC2rOdsJ/RNpyD2S/Fz/q6nNrPrqc1ntsfZCLjte/dTu+4qPxxNbC+sqrsxWBC7QVv2PymtOzjjyl2efONzpKWN5tZ2sq4rSusObdn/FCyI3VBYVXdjOJrYXrvuKn/b9+4/KyWfdQ18063XAnDxB38rO3/FymdR6sWc6y0PhkLrLdu+zMv5q1GqAph1/ABgBO/7P91yrKCgYHcq6+0IFcS2ofU7BfHC3NG3d/Pmi0/Dpr89W/Hfm/86++SRLFeVuehoNPj4E88t3fz4z9ZlUskLldILgMUiphIhYcxYV1ZbBsWQUroLaBIx7waC4b2i1K7epHcwIans6LxVvPHgXedc1n8F/rphWEnA06IAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDktMDhUMTI6NDI6MDktMDQ6MDD8ntmGAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTA5LTA4VDEyOjQyOjA5LTA0OjAwjcNhOgAAAABJRU5ErkJggg=='
);How to register and authenticate my users?
<?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);
$data = '
{
"id":"KVb8CnwDjpgAo[…]op61BTLaa0tczXvz4JrQ23usxVHA8QJZi3L9GZLsAtkcVvWObA",
"type":"public-key",
"rawId":"KVb8CnwDjpgAo[…]rQ23usxVHA8QJZi3L9GZLsAtkcVvWObA==",
"response":{
"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJQbk1hVjBVTS[…]1iUkdHLUc4Y3BDSdGUifQ==",
"attestationObject":"o2NmbXRmcGFja2VkZ2F0dFN0bXSj[…]YcGhf"
}
}';
$publicKeyCredential = $publicKeyCredentialLoader->load($data);<?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'
);security:
providers:
default:
id: App\Security\UserProvider
firewalls:
main:
logout:
path: 'logout'
...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}security:
firewalls:
main:
webauthn:
authentication:
routes:
options_path: '/assertion/options'
result_path: '/assertion/result'
access_control:
- { path: ^/assertion, roles: PUBLIC_ACCESS, requires_channel: 'https'}security:
firewalls:
main:
webauthn:
authentication:
profile: 'acme'security:
firewalls:
main:
webauthn:
registration:
enabled: truesecurity:
access_control:
- { path: ^/register, roles: PUBLIC_ACCESS}security:
access_control:
- { path: ^/admin, roles: [ROLE_ADMIN, IS_USER_VERIFIED]}security:
firewalls:
main:
webauthn:
authentication:
options_handler: 'App\Handler\MyCustomRequestOptionsHandler'ecurity:
firewalls:
main:
webauthn:
registration:
options_handler: 'App\Handler\MyCustomRequestOptionsHandler'security:
firewalls:
main:
webauthn:
success_handler: 'App\Handler\MyCustomAuthenticationSuccessHandler'security:
firewalls:
main:
webauthn:
failure_handler: 'App\Handler\MyCustomAuthenticationFailureHandler'<?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;
}
}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 examplesecurity:
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'))
;webauthn:
controllers:
enabled: true
creation:
from_user_account:
…
profile: custom_profilewebauthn:
controllers:
enabled: true
creation:
from_user_account:
…
options_handler: … # Your handler herewebauthn:
controllers:
enabled: true
creation:
from_user_account:
…
success_handler: … # Your handler herewebauthn:
controllers:
enabled: true
creation:
from_user_account:
…
failure_handler: … # Your handler herewebauthn:
# 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'webauthn:
creation_profiles:
acme:
rp:
name: 'ACME Webauthn Server'
challenge_length: 16webauthn:
creation_profiles:
acme:
rp:
name: 'ACME Webauthn Server'
timeout: 30000webauthn:
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_REQUIREDwebauthn:
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_RS256webauthn:
creation_profiles:
acme:
rp:
name: 'ACME Webauthn Server'
attestation_conveyance: !php/const Webauthn\PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECTwebauthn:
creation_profiles:
acme:
rp:
name: 'ACME Webauthn Server'
extensions:
loc: true
txAuthSimple: 'Please add your new authenticator'webauthn:
request_profiles:
acme:
rp_id: 'example.com'webauthn:
request_profiles:
acme: ~preferred or required, the range is 300 to 600 seconds (5 to 10 minutes)<?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\AttestationStatement\AttestationObjectLoader;
$attestationObjectLoader = AttestationObjectLoader::create(
$attestationStatementSupportManager
);<?php
declare(strict_types=1);
use Webauthn\PublicKeyCredentialLoader;
$publicKeyCredentialLoader = PublicKeyCredentialLoader::create(
$attestationObjectLoader
);<?php
declare(strict_types=1);
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
$extensionOutputCheckerHandler = ExtensionOutputCheckerHandler::create();<?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()
)
;<?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(),
)
;<?php
declare(strict_types=1);
use Webauthn\AuthenticatorAttestationResponseValidator;
$authenticatorAttestationResponseValidator = AuthenticatorAttestationResponseValidator::create(
$attestationStatementSupportManager,
null, //Deprecated Public Key Credential Source Repository. Please set null.
null, //Deprecated Token Binding Handler. Please set null.
$extensionOutputCheckerHandler
);<?php
declare(strict_types=1);
use Webauthn\AuthenticatorAssertionResponseValidator;
$authenticatorAssertionResponseValidator = AuthenticatorAssertionResponseValidator::create(
null, //Deprecated Public Key Credential Source Repository. Please set null.
null, //Deprecated Token Binding Handler. Please set null.
$extensionOutputCheckerHandler, // The extension output checker handler
$algorithmManager // The COSE Algorithm Manager
);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 = $publicKeyCredentialLoader->load($data);<?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'
);
// 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);
Where the public keys and details are stored
<?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;
}
}<?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);
}
}
services:
App\Repository\WebauthnCredentialRepository: ~