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...
Is it complicated?
Adoption by web browsers
You have just found a bug?
Registration and Authentication process overview
How to run a basic Webauthn server?
<?php
use Webauthn\Server;
use Webauthn\PublicKeyCredentialRpEntity;
$rpEntity = new PublicKeyCredentialRpEntity(
'Webauthn Server',
'my.domain.com'
);
$publicKeyCredentialSourceRepository = …; //Your repository here. Must implement Webauthn\PublicKeyCredentialSourceRepository
$server = new Server(
$rpEntity
$publicKeyCredentialSourceRepository
);composer require web-auth/webauthn-lib





$server->setSecuredRelyingPartyId(['localhost']);<?php
use Webauthn\PublicKeyCredentialUserEntity;
use Webauthn\PublicKeyCredentialCreationOptions;
$userEntity = new PublicKeyCredentialUserEntity(
'john.doe',
'ea4e7b55-d8d0-4c7e-bbfa-78ca96ec574c',
'John Doe'
);
/** This avoids multiple registration of the same authenticator with the user account **/
/** You can remove this code if it is a new user **/
// Get the list of authenticators associated to the user
$credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity);
// Convert the Credential Sources into Public Key Credential Descriptors
$excludeCredentials = array_map(function (PublicKeyCredentialSource $credential) {
return $credential->getPublicKeyCredentialDescriptor();
}, $credentialSources);
/** End of optional part**/
$publicKeyCredentialCreationOptions = $server->generatePublicKeyCredentialCreationOptions(
$userEntity, // The user entity
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, // We will see this option later
$excludeCredentials // Excluded authenticators
// Set [] if new user
);{
"id": "LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
"rawId": "LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
"response": {
"clientDataJSON": "eyJjaGFsbGVuZ2UiOiJOeHlab3B3VktiRmw3RW5uTWFlXzVGbmlyN1FKN1FXcDFVRlVLakZIbGZrIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9",
"attestationObject": "o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIgVzzvX3Nyp_g9j9f2B-tPWy6puW01aZHI8RXjwqfDjtQCIQDLsdniGPO9iKr7tdgVV-FnBYhvzlZLG3u28rVt10YXfGN4NWOBWQJOMIICSjCCATKgAwIBAgIEVxb3wDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLDEqMCgGA1UEAwwhWXViaWNvIFUyRiBFRSBTZXJpYWwgMjUwNTY5MjI2MTc2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZNkcVNbZV43TsGB4TEY21UijmDqvNSfO6y3G4ytnnjP86ehjFK28-FdSGy9MSZ-Ur3BVZb4iGVsptk5NrQ3QYqM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAHibGMqbpNt2IOL4i4z96VEmbSoid9Xj--m2jJqg6RpqSOp1TO8L3lmEA22uf4uj_eZLUXYEw6EbLm11TUo3Ge-odpMPoODzBj9aTKC8oDFPfwWj6l1O3ZHTSma1XVyPqG4A579f3YAjfrPbgj404xJns0mqx5wkpxKlnoBKqo1rqSUmonencd4xanO_PHEfxU0iZif615Xk9E4bcANPCfz-OLfeKXiT-1msixwzz8XGvl2OTMJ_Sh9G9vhE-HjAcovcHfumcdoQh_WM445Za6Pyn9BZQV3FCqMviRR809sIATfU5lu86wu_5UGIGI7MFDEYeVGSqzpzh6mlcn8QSIZoYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAsV2gIUlPIHzZnNIlQdz5zvbKtpFz_WY-8ZfxOgTyy7f3Ffbolyp3fUtSQo5LfoUgBaBaXqK0wqqYO-u6FrrLApQECAyYgASFYIPr9-YH8DuBsOnaI3KJa0a39hyxh9LDtHErNvfQSyxQsIlgg4rAuQQ5uy4VXGFbkiAt0uwgJJodp-DymkoBcrGsLtkI"
},
"type": "public-key"
}<?php
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;
$psr17Factory = new Psr17Factory();
$creator = new ServerRequestCreator(
$psr17Factory, // ServerRequestFactory
$psr17Factory, // UriFactory
$psr17Factory, // UploadedFileFactory
$psr17Factory // StreamFactory
);
$serverRequest = $creator->fromGlobals();
try {
$publicKeyCredentialSource = $server->loadAndCheckAttestationResponse(
'_The authenticator response you received…',
$publicKeyCredentialCreationOptions, // The options you stored during the previous step
$serverRequest // The PSR-7 request
);
// The user entity and the public key credential source can now be stored using their repository
// The Public Key Credential Source repository must implement Webauthn\PublicKeyCredentialSourceRepository
$publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource);
// If you create a new user account, you should also save the user entity
$userEntityRepository->save($userEntity);
} catch(\Throwable $exception) {
// Something went wrong!
}<?php
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialUserEntity;
// UseEntity found using the username.
$userEntity = $userEntityRepository->findWebauthnUserByUsername('john.doe');
// Get the list of authenticators associated to the user
$credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity);
// Convert the Credential Sources into Public Key Credential Descriptors
$allowedCredentials = array_map(function (PublicKeyCredentialSource $credential) {
return $credential->getPublicKeyCredentialDescriptor();
}, $credentialSources);
// We generate the set of options.
$publicKeyCredentialRequestOptions = $server->generatePublicKeyCredentialRequestOptions(
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, // Default value
$allowedCredentials
);{
"id":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
"rawId":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
"response":{
"authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAAAA",
"signature":"MEYCIQCv7EqsBRtf2E4o_BjzZfBwNpP8fLjd5y6TUOLWt5l9DQIhANiYig9newAJZYTzG1i5lwP-YQk9uXFnnDaHnr2yCKXL",
"userHandle":"",
"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJ4ZGowQ0JmWDY5MnFzQVRweTBrTmM4NTMzSmR2ZExVcHFZUDh3RFRYX1pFIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmdldCJ9"
},
"type":"public-key"
}<?php
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;
$psr17Factory = new Psr17Factory();
$creator = new ServerRequestCreator(
$psr17Factory, // ServerRequestFactory
$psr17Factory, // UriFactory
$psr17Factory, // UploadedFileFactory
$psr17Factory // StreamFactory
);
$serverRequest = $creator->fromGlobals();
try {
$publicKeyCredentialSource = $server->loadAndCheckAssertionResponse(
'_The authenticator response you received…',
$publicKeyCredentialRequestOptions, // The options you stored during the previous step
$userEntity, // The user entity
$serverRequest // The PSR-7 request
);
//If everything is fine, this means the user has correctly been authenticated using the
// authenticator defined in $publicKeyCredentialSource
} catch(\Throwable $exception) {
// Something went wrong!
}<?php
use App\Service\MyPsr3Logger;
use Webauthn\Server;
$server = new Server(
$rpEntity,
$publicKeyCredentialSourceRepository
);
// Set your logging service here
$server->setLogger(new MyPsr3Logger());webauthn:
logger: App\Service\MyPsr3Logger$publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check(
$authenticatorAttestationResponse,
$publicKeyCredentialCreationOptions,
$serverRequest,
['localhost']
);$publicKeyCredentialSource = $authenticatorAssertionResponse->check(
$publicKeyCredential->getRawId(),
$authenticatorAssertionResponse,
$publicKeyCredentialRequestOptions,
$request,
$userHandle,
['localhost']
);security:
firewalls:
main:
webauthn:
secured_rp_ids:
- 'localhost'<?php
use Webauthn\PublicKeyCredentialRpEntity;
$rpEntity = new PublicKeyCredentialRpEntity(
'ACME Webauthn Server' // The application name
);https://user:[email protected]<?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;
}
}
}npm i @web-auth/webauthn-helper
#or
yarn add @web-auth/webauthn-helper// 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: '/api/register',
optionsUrl: '/api/register/options'
});
// We can call this register function whenever we need (e.g. form submission)
register({
username: 'john.doe',
displayName: 'JD'
})
.then((response) => console.log('Registration success'))
.catch((error) => console.log('Registration failure'))
;register({
username: 'john.doe',
displayName: 'JD',
attestation: 'none',
authenticatorSelection: {
authenticatorAttachment: 'platform',
requireResidentKey: true,
userVerification: 'required'
}
})
.then((response) => console.log('Registration success'))
.catch((error) => console.log('Registration failure'))
;// Import the login hook
import {useLogin} from 'webauthn-helper';
// Create your login function.
// By default the urls are "/login" and "/login/options"
// but you can change those urls if needed.
const login = useLogin({
actionUrl: '/api/login',
optionsUrl: '/api/login/options'
});
// We can call this login function whenever we need (e.g. form submission)
login({
username: 'john.doe'
})
.then((response) => console.log('Authentication success'))
.catch((error) => console.log('Authentication failure'))
;login({
username: 'john.doe',
userVerification: 'required'
})
.then((response) => console.log('Authentication success'))
.catch((error) => console.log('Authentication failure'))
;<?php
use Webauthn\PublicKeyCredentialRpEntity;
$rpEntity = new PublicKeyCredentialRpEntity(
'ACME Webauthn Server', // The application name
'acme.com' // The application ID = the domain
);<?php
use Webauthn\PublicKeyCredentialRpEntity;
$rpEntity = new PublicKeyCredentialRpEntity(
'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
use Webauthn\Server;
$server = new Server(
$rpEntity
$publicKeyCredentialSourceRepository
);
// Set your handler here
$server->setCounterChecker(new CustomCounterChecker());$authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator(
$publicKeyCredentialSourceRepository,
$tokenBindingHandler,
$extensionOutputCheckerHandler,
$coseAlgorithmManager,
new CustomCounterChecker()
);webauthn:
counter_checker: App\Service\CustomCounterChecker<?php
use Webauthn\Server;
use Webauthn\TokenBinding\TokenBindingNotSupportedHandler;
$server = new Server(
$rpEntity
$publicKeyCredentialSourceRepository
);
// Set your handler here
$server->setTokenBindingHandler(new TokenBindingNotSupportedHandler());webauthn:
token_binding_support_handler: Webauthn\TokenBinding\TokenBindingNotSupportedHandlerIf you want a fine grained Webauthn server
// We gather all registered authenticators for this 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\PublicKeyCredentialRequestOptions;
// 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 = new PublicKeyCredentialRequestOptions(
random_bytes(32), // Challenge
60000, // Timeout
'foo.example.com', // Relying Party ID
$allowedCredentials // Extensions
);{
"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;
$authenticatorAssertionResponse = $publicKeyCredential->getResponse();
if (!$authenticatorAssertionResponse instanceof AuthenticatorAssertionResponse) {
//e.g. process here with a redirection to the public key login/MFA page.
}<?php
declare(strict_types=1);
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
$publicKeyCredentialSource = $authenticatorAssertionResponseValidator->check(
$publicKeyCredential->getRawId(),
$authenticatorAssertionResponse,
$publicKeyCredentialRequestOptions,
$request,
$userHandle
);<?php
/**
* EGroupware WebAuthn
*
* @link https://www.egroupware.org
* @author Ralf Becker <rb-At-egroupware.org>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
namespace Acme\Repository;
use Webauthn\PublicKeyCredentialSourceRepository as PublicKeyCredentialSourceRepositoryInterface;
use Webauthn\PublicKeyCredentialSource;
use Webauthn\PublicKeyCredentialUserEntity;
class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRepositoryInterface
{
private $path = '/tmp/pubkey-repo.json';
public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource
{
$data = $this->read();
if (isset($data[base64_encode($publicKeyCredentialId)]))
{
return PublicKeyCredentialSource::createFromArray($data[base64_encode($publicKeyCredentialId)]);
}
return null;
}
/**
* @return PublicKeyCredentialSource[]
*/
public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array
{
$sources = [];
foreach($this->read() as $data)
{
$source = PublicKeyCredentialSource::createFromArray($data);
if ($source->getUserHandle() === $publicKeyCredentialUserEntity->getId())
{
$sources[] = $source;
}
}
return $sources;
}
public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void
{
$data = $this->read();
$data[base64_encode($publicKeyCredentialSource->getPublicKeyCredentialId())] = $publicKeyCredentialSource;
$this->write($data);
}
private function read(): array
{
if (file_exists($this->path))
{
return json_decode(file_get_contents($this->path), true);
}
return [];
}
private function write(array $data): void
{
if (!file_exists($this->path))
{
if (!mkdir($concurrentDirectory = dirname($this->path), 0700, true) && !is_dir($concurrentDirectory)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
}
}
file_put_contents($this->path, json_encode($data), LOCK_EX);
}
}<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2019 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Webauthn\Repository;
use Assert\Assertion;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManagerInterface;
use Webauthn\PublicKeyCredentialSource;
use Webauthn\PublicKeyCredentialSourceRepository as PublicKeyCredentialSourceRepositoryInterface;
use Webauthn\PublicKeyCredentialUserEntity;
class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRepositoryInterface, ServiceEntityRepositoryInterface
{
/**
* @var EntityManagerInterface
*/
private $manager;
/**
* @var string
*/
private $class;
public function __construct(ManagerRegistry $registry, string $class)
{
Assertion::subclassOf($class, PublicKeyCredentialSource::class, sprintf(
'Invalid class. Must be an instance of "Webauthn\PublicKeyCredentialSource", got "%s" instead.',
$class
));
$manager = $registry->getManagerForClass($class);
Assertion::isInstanceOf($manager, EntityManagerInterface::class, sprintf(
'Could not find the entity manager for class "%s". Check your Doctrine configuration to make sure it is configured to load this entity’s metadata.',
$class
));
$this->class = $class;
$this->manager = $manager;
}
protected function getClass(): string
{
return $this->class;
}
protected function getEntityManager(): EntityManagerInterface
{
return $this->manager;
}
public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource, bool $flush = true): void
{
$this->manager->persist($publicKeyCredentialSource);
if ($flush) {
$this->manager->flush();
}
}
/**
* {@inheritdoc}
*/
public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array
{
$qb = $this->manager->createQueryBuilder();
return $qb->select('c')
->from($this->getClass(), 'c')
->where('c.userHandle = :userHandle')
->setParameter(':userHandle', $publicKeyCredentialUserEntity->getId())
->getQuery()
->execute()
;
}
public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource
{
$qb = $this->manager->createQueryBuilder();
return $qb->select('c')
->from($this->getClass(), 'c')
->where('c.publicKeyCredentialId = :publicKeyCredentialId')
->setParameter(':publicKeyCredentialId', base64_encode($publicKeyCredentialId))
->setMaxResults(1)
->getQuery()
->getOneOrNullResult()
;
}
}<?php
declare(strict_types=1);
namespace App\Service;
use Assert\Assertion;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
use Webauthn\Bundle\Security\Guesser\UserEntityGuesser;
use Webauthn\PublicKeyCredentialUserEntity;
final class CurrentUserEntityGuesser implements UserEntityGuesser
{
/**
* @var Security
*/
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function findUserEntity(Request $request): PublicKeyCredentialUserEntity
{
$user = $this->security->getUser();
Assertion::isInstanceOf($user, PublicKeyCredentialUserEntity::class, 'Unable to find the user entity');
return $user;
}
}webauthn:
controllers:
enabled: true # We enable the feature
creation:
from_user_account: # Unique name of our endpoints
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: App\Service\CurrentUserEntityGuesser # See above
from_admin_dashboard: # Unique name of our endpoints
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\Service\AdminGuesser # Fictive servicesecurity:
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 here<?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 = new AttestationStatementSupportManager();
// The none type
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport());<?php
declare(strict_types=1);
use Webauthn\AttestationStatement\AttestationObjectLoader;
$attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager);<?php
declare(strict_types=1);
use Webauthn\PublicKeyCredentialLoader;
$publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader);<?php
declare(strict_types=1);
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
$extensionOutputCheckerHandler = new ExtensionOutputCheckerHandler();<?php
declare(strict_types=1);
use Webauthn\AuthenticatorAttestationResponseValidator;
$authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator(
$attestationStatementSupportManager,
$publicKeyCredentialSourceRepository,
$tokenBindingHandler,
$extensionOutputCheckerHandler
);<?php
declare(strict_types=1);
use Webauthn\AuthenticatorAssertionResponseValidator;
$authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator(
$publicKeyCredentialSourceRepository, // The Credential Repository service
$tokenBindingHandler, // The token binding handler
$extensionOutputCheckerHandler, // The extension output checker handler
$coseAlgorithmManager // The COSE Algorithm Manager
);use Webauthn\AuthenticatorSelectionCriteria;
$authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria(
null,
false,
AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED
);
$publicKeyCredentialCreationOptions = $server->generatePublicKeyCredentialCreationOptions(
$userEntity,
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
$excludeCredentials
$authenticatorSelectionCriteria
);use Webauthn\AuthenticatorSelectionCriteria;
$publicKeyCredentialRequestOptions = $server->generatePublicKeyCredentialRequestOptions(
AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED,
$allowedAuthenticators
);$authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria(
null,
false,
AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED
);
$publicKeyCredentialCreationOptions = new PublicKeyCredentialCreationOptions(
$rpEntity,
$userEntity,
$challenge,
$publicKeyCredentialParametersList,
$timeout,
$excludedPublicKeyDescriptors,
$authenticatorSelectionCriteria
);// Public Key Credential Request Options
$publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions(
random_bytes(32),
60000,
'foo.example.com',
$allowedCredentials,
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_DISCOURAGED
);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<?php
declare(strict_types=1);
use Cose\Algorithms;
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialDescriptor;
use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialParameters;
use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialUserEntity;
// RP Entity
$rpEntity = new PublicKeyCredentialRpEntity(
'My Super Secured Application', //Name
'foo.example.com', //ID
null //Icon
);
// User Entity
$userEntity = new PublicKeyCredentialUserEntity(
'@cypher-Angel-3000', //Name
'123e4567-e89b-12d3-a456-426655440000', //ID
'Mighty Mike', //Display name
null //Icon
);
// Challenge
$challenge = random_bytes(16);
// Timeout
$timeout = 60000; // 60 seconds
// Public Key Credential Parameters
$publicKeyCredentialParametersList = [
new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_ES256),
new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_RS256),
];
// Devices to exclude
$excludedPublicKeyDescriptors = [
new PublicKeyCredentialDescriptor(PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, 'ABCDEFGH…'),
];
$publicKeyCredentialCreationOptions = new PublicKeyCredentialCreationOptions(
$rpEntity,
$userEntity,
$challenge,
$publicKeyCredentialParametersList,
$timeout,
$excludedPublicKeyDescriptors,
new AuthenticatorSelectionCriteria(),
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
null // Extensions
);{
"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;
$authenticatorAttestationResponse = $publicKeyCredential->getResponse();
if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) {
//e.g. process here with a redirection to the public key creation page.
}<?php
declare(strict_types=1);
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;
$psr17Factory = new Psr17Factory();
$creator = new ServerRequestCreator(
$psr17Factory, // ServerRequestFactory
$psr17Factory, // UriFactory
$psr17Factory, // UploadedFileFactory
$psr17Factory // StreamFactory
);
$serverRequest = $creator->fromGlobals();
$publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check(
$authenticatorAttestationResponse,
$publicKeyCredentialCreationOptions,
$serverRequest
);<?php
use Webauthn\PublicKeyCredentialUserEntity;
$userEntity = new PublicKeyCredentialUserEntity(
'john.doe', // Username
'ea4e7b55-d8d0-4c7e-bbfa-78ca96ec574c', // ID
'John Doe' // Display name
);<?php
use Webauthn\PublicKeyCredentialUserEntity;
$userEntity = new PublicKeyCredentialUserEntity(
'john.doe',
'ea4e7b55-d8d0-4c7e-bbfa-78ca96ec574c',
'John Doe',
'data:image/png;base64,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'
);<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2019 Spomky-Labs
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
namespace Acme\Repository;
use Webauthn\PublicKeyCredentialUserEntity;
final class PublicKeyCredentialUserEntityRepository
{
public function findWebauthnUserByUsername(string $username): ?PublicKeyCredentialUserEntity
{
//We suppose you already have a method to find a user using its username
$user = $this->findOneBy(['username' => $username]);
if (null === $user) {
return null;
}
return $this->createUserEntity($user);
}
public function findWebauthnUserByUserHandle(string $userHandle): ?PublicKeyCredentialUserEntity
{
//We suppose you already have a method to find a user using its ID
$user = $this->findOneBy(['id' => $userHandle]);
if (null === $user) {
return null;
}
return $this->createUserEntity($user);
}
private function createUserEntity(User $user): PublicKeyCredentialUserEntity
{
//We create a PublicKeyCredentialUserEntity object
// This object requires the username, the ID and the name to display (e.g. "John Doe")
// The avatar URL is optional and could be null
return new PublicKeyCredentialUserEntity(
$user->username,
$user->id,
$user->displayName,
$user->avatarUrl
);
}
}use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialCreationOptions;
$authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE,
true, // Resident key required
AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED // User verification required
);<?php
use Webauthn\PublicKeyCredentialRequestOptions;
$ublicKeyCredentialRequestOptions = $server->generatePublicKeyCredentialRequestOptions(
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED,
);$authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE,
true, // Resident key required
AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED // User verification required
);
$publicKeyCredentialCreationOptions = new PublicKeyCredentialCreationOptions(
$rpEntity,
$userEntity,
$challenge,
$publicKeyCredentialParametersList,
$timeout,
$excludedPublicKeyDescriptors,
$authenticatorSelectionCriteria
);// Public Key Credential Request Options
$publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions(
random_bytes(32),
60000,
'foo.example.com',
[],
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED
);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
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialCreationOptions;
$authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM, // Platform authenticator
true, // Resident key required
AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED // User verification required
);
$publicKeyCredentialCreationOptions = $server->generatePublicKeyCredentialCreationOptions(
$userEntity,
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
$excludedPublicKeyDescriptors,
$authenticatorSelectionCriteria
);$authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria(
null,
false,
AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED
);
$publicKeyCredentialCreationOptions = new PublicKeyCredentialCreationOptions(
$rpEntity,
$userEntity,
$challenge,
$publicKeyCredentialParametersList,
$timeout,
$excludedPublicKeyDescriptors,
$authenticatorSelectionCriteria
);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_PREFERREDuse Webauthn\AuthenticatorAssertionResponseValidator;
//Before:
$validator = new AuthenticatorAssertionResponseValidator(
$publicKeyCredentialSourceRepository,
$decoder,
$tokenBindingHandler,
$extensionOutputCheckerHandler,
$algorithmManager
);
//After
$validator = new AuthenticatorAssertionResponseValidator(
$publicKeyCredentialSourceRepository,
$tokenBindingHandler,
$extensionOutputCheckerHandler,
$algorithmManager
);spomky-labs/cbor-bundle^1.0^2.0<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Webauthn\PublicKeyCredentialSource as BasePublicKeyCredentialSource;
use Webauthn\TrustPath\TrustPath;
/**
* @ORM\Table(name="public_key_credential_sources")
* @ORM\Entity(repositoryClass="App\Repository\PublicKeyCredentialSourceRepository")
*/
class PublicKeyCredentialSource extends BasePublicKeyCredentialSource
{
/**
* @var string
* @ORM\Id
* @ORM\Column(type="string", length=100)
* @ORM\GeneratedValue(strategy="NONE")
*/
private $id;
public function __construct(string $publicKeyCredentialId, string $type, array $transports, string $attestationType, TrustPath $trustPath, UuidInterface $aaguid, string $credentialPublicKey, string $userHandle, int $counter)
{
$this->id = Uuid::uuid4()->toString();
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\Common\Persistence\ManagerRegistry;
use Webauthn\Bundle\Repository\PublicKeyCredentialSourceRepository as BasePublicKeyCredentialSourceRepository;
use Webauthn\PublicKeyCredentialSource as BasePublicKeyCredentialSource;
final class PublicKeyCredentialSourceRepository extends BasePublicKeyCredentialSourceRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, PublicKeyCredentialSource::class);
}
public function saveCredentialSource(BasePublicKeyCredentialSource $publicKeyCredentialSource, bool $flush = true): void
{
if (!$publicKeyCredentialSource instanceof PublicKeyCredentialSource) {
$publicKeyCredentialSource = new PublicKeyCredentialSource(
$publicKeyCredentialSource->getPublicKeyCredentialId(),
$publicKeyCredentialSource->getType(),
$publicKeyCredentialSource->getTransports(),
$publicKeyCredentialSource->getAttestationType(),
$publicKeyCredentialSource->getTrustPath(),
$publicKeyCredentialSource->getAaguid(),
$publicKeyCredentialSource->getCredentialPublicKey(),
$publicKeyCredentialSource->getUserHandle(),
$publicKeyCredentialSource->getCounter()
);
}
parent::saveCredentialSource($publicKeyCredentialSource, $flush);
}
}services:
App\Repository\PublicKeyCredentialSourceRepository: ~<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Webauthn\PublicKeyCredentialUserEntity;
/**
* @ORM\Table(name="users")
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
* @UniqueEntity("name")
*/
class User extends PublicKeyCredentialUserEntity implements UserInterface
{
/**
* @ORM\Id
* @ORM\Column(type="string", length=255)
*/
protected $id;
/**
* @ORM\Column(type="json")
*/
protected $roles;
public function __construct(string $id, string $name, string $displayName, ?string $icon = null, array $roles = [])
{
parent::__construct($name, $id, $displayName, $icon);
$this->roles = $roles;
}
public function getRoles(): array
{
return array_unique($this->roles + ['ROLE_USER']);
}
public function getPassword(): void
{
}
public function getSalt(): void
{
}
public function getUsername(): ?string
{
return $this->name;
}
public function eraseCredentials(): void
{
}
}<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\User;
use Doctrine\Common\Persistence\ManagerRegistry;
use Ramsey\Uuid\Uuid;
use Webauthn\Bundle\Repository\AbstractPublicKeyCredentialUserEntityRepository;
use Webauthn\PublicKeyCredentialUserEntity;
final class UserRepository extends AbstractPublicKeyCredentialUserEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function createUserEntity(string $username, string $displayName, ?string $icon): PublicKeyCredentialUserEntity
{
$id = Uuid::uuid4()->toString();
return new User($id, $username, $displayName, $icon, []);
}
public function saveUserEntity(PublicKeyCredentialUserEntity $userEntity): void
{
if (!$userEntity instanceof User) {
$userEntity = new User(
$userEntity->getId(),
$userEntity->getName(),
$userEntity->getDisplayName(),
$userEntity->getId()
);
}
parent::saveUserEntity($userEntity);
}
public function find(string $username): ?User
{
$qb = $this->getEntityManager()->createQueryBuilder();
return $qb->select('u')
->from(User::class, 'u')
->where('u.name = :name')
->setParameter(':name', $username)
->setMaxResults(1)
->getQuery()
->getOneOrNullResult()
;
}
}services:
App\Repository\UserRepository: ~<?php
declare(strict_types=1);
use Webauthn\AuthenticationExtensions\AuthenticationExtension;
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
use Webauthn\PublicKeyCredentialRequestOptions;
// Extensions
$extensions = new AuthenticationExtensionsClientInputs();
$extensions->add(new AuthenticationExtension('loc', true));
// List of registered PublicKeyCredentialDescriptor classes associated to the user
$registeredPublicKeyCredentialDescriptors = …;
// Public Key Credential Request Options
$publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions(
random_bytes(32), // Challenge
60000, // Timeout
'foo.example.com', // Relying Party ID
$registeredPublicKeyCredentialDescriptors, // Registered PublicKeyCredentialDescriptor classes
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, // User verification requirement
$extensions
);<?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);
use Webauthn\AuthenticationExtensions\AuthenticationExtension;
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
// Extensions
$extensions = new AuthenticationExtensionsClientInputs();
$extensions->add(new AuthenticationExtension('loc', true));
$publicKeyCredentialCreationOptions = $server->generatePublicKeyCredentialCreationOptions(
$userEntity,
$attestationMode,
$excludedPublicKeyDescriptors, $authenticatorSelectionCriteria,
$extensions
);<?php
use Webauthn\AuthenticationExtensions\AuthenticationExtension;
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
use Webauthn\PublicKeyCredentialCreationOptions;
// Extensions
$extensions = new AuthenticationExtensionsClientInputs();
$extensions->add(new AuthenticationExtension('loc', true));
$publicKeyCredentialRequestOptions = $server->generatePublicKeyCredentialRequestOptions(
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED,
$allowedCredentials
$extensions
);<?php
declare(strict_types=1);
use Webauthn\AuthenticationExtensions\AuthenticationExtension;
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
use Webauthn\PublicKeyCredentialCreationOptions;
// Extensions
$extensions = new AuthenticationExtensionsClientInputs();
$extensions->add(new AuthenticationExtension('loc', true));
$publicKeyCredentialCreationOptions = new PublicKeyCredentialCreationOptions(
$rpEntity,
$userEntity,
$challenge,
$publicKeyCredentialParametersList,
$timeout,
$excludedPublicKeyDescriptors,
$authenticatorSelectionCriteria,
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
$extensions
);<?php
declare(strict_types=1);
use Webauthn\AuthenticationExtensions\AuthenticationExtension;
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
use Webauthn\PublicKeyCredentialRequestOptions;
// Extensions
$extensions = new AuthenticationExtensionsClientInputs();
$extensions->add(new AuthenticationExtension('loc', true));
// 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 = new PublicKeyCredentialRequestOptions(
random_bytes(32), // Challenge
60000, // Timeout
'foo.example.com', // Relying Party ID
$allowedCredentials, // Registered PublicKeyCredentialDescriptor classes
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, // User verification requirement
$extensions // Extensions
);webauthn:
…
creation_profiles:
default:
rp:
name: 'My Application'
id: 'example.com'
extensions:
loc: true
request_profiles:
default:
rp_id: 'example.com'
extensions:
loc: truecomposer require symfony/psr-http-message-bridge nyholm/psr7 annotationssensio_framework_extra:
psr_message:
enabled: true<?php
public function registerBundles()
{
$bundles = [
// ...
new Webauthn\Bundle\WebauthnBundle(),
];
}<?php
declare(strict_types=1);
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return function (RoutingConfigurator $routes) {
$routes->import('.', 'webauthn');
};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_service:
# enabled: false
# repository: 'App\Repository\MetadataStatementRepository'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: ~<?php
use Webauthn\Server;
use Webauthn\PublicKeyCredentialRpEntity;
...
// Before v3.3
$server = new Server(
$rpEntity,
$publicKeyCredentialSourceRepository,
$myMetadataStatementRepository // Inject your new service here
);
// Or v3.3+
$server = new Server(
$rpEntity,
$publicKeyCredentialSourceRepository
);
$server->setMetadataStatementRepository($myMetadataStatementRepository); // Inject your new service here<?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;
// You normally already do this
$attestationStatementSupportManager = new AttestationStatementSupportManager();
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport());
// Additional classes to add
$attestationStatementSupportManager->add(new FidoU2FAttestationStatementSupport());
$attestationStatementSupportManager->add(new AppleAttestationStatementSupport());
$attestationStatementSupportManager->add(new AndroidSafetyNetAttestationStatementSupport());
$attestationStatementSupportManager->add(new AndroidKeyAttestationStatementSupport(
$psr18Client, // Can be null if you don’t want to use the Google API
$googleApiKey, // Can be null if you don’t want to use the Google API
$psr17RequestFactory // Can be null if you don’t want to use the Google API
));
$attestationStatementSupportManager->add(new TPMAttestationStatementSupport());
// 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 = new Manager();
$coseAlgorithmManager->add(new ECDSA\ES256());
$coseAlgorithmManager->add(new RSA\RS256());
$attestationStatementSupportManager->add(new PackedAttestationStatementSupport($coseAlgorithmManager));$attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager, $metadataStatementRepository);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 0<?php
use Webauthn\PublicKeyCredentialCreationOptions;
$publicKeyCredentialCreationOptions = $server->generatePublicKeyCredentialCreationOptions(
$userEntity,
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT,
);<?php
use Webauthn\PublicKeyCredentialCreationOptions;
$publicKeyCredentialCreationOptions = new PublicKeyCredentialCreationOptions(
$relyingParty
$userEntity,
$challenge,
$pubKeyCredParams,
$timeout,
$excludeCredentials,
$authenticatorSelection,
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT
);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'How to register and authenticate my users?
composer require symfony/security-bundle<?php
namespace App\Security;
use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class UserProvider implements UserProviderInterface
{
/**
* @var UserRepository
*/
private $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
/**
* @throws UsernameNotFoundException if the user is not found
*/
public function loadUserByUsername($username): UserInterface
{
$user = $this->userRepository->find($username);
if (null !== $user) {
return $user;
}
throw new UsernameNotFoundException(sprintf('User with name "%s" does not exist', $username));
}
public function refreshUser(UserInterface $user): UserInterface
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class): bool
{
return User::class === $class;
}
}security:
providers:
main:
id: 'App\Security\UserProvider'
firewalls:
main:
webauthn: ~security:
providers:
backend_users:
memory:
users:
john_admin: { password: '$2y$13$jxGxc ... IuqDju', roles: ['ROLE_ADMIN'] }
jane_admin: { password: '$2y$13$PFi1I ... rGwXCZ', roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN'] }
firewalls:
backend:
pattern: ^/backend/
# ... Add your configuration here
main:
webauthn:
user_provider: 'App\Security\UserProvider'security:
# ...
firewalls:
main:
# ...
logout: ~security:
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }fetch('/login/options', {
method : 'POST',
credentials : 'same-origin',
headers : {
'Content-Type' : 'application/json'
},
body: JSON.stringify({
"username": "[email protected]"
})
}).then(function (response) {
return response.json();
}).then(function (json) {
console.log(json);
}).catch(function (err) {
console.log({ 'status': 'failed', 'error': err });
})security:
firewalls:
main:
webauthn:
authentication:
routes:
options_path: /security/authentication/options
access_control:
- { path: ^/security/authentication/options, roles: IS_AUTHENTICATED_ANONYMOUSLY}fetch('/login', {
method : 'POST',
credentials : 'same-origin',
headers : {
'Content-Type' : 'application/json'
},
body: //put the security device response here
}).then(function (response) {
return response.json();
}).then(function (json) {
console.log(json);
}).catch(function (err) {
console.log({ 'status': 'failed', 'error': err });
})security:
firewalls:
main:
webauthn:
authentication:
routes:
result_path: /security/authentication/login<?php
declare(strict_types=1);
namespace Acme\Controller;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
final class AdminController
{
/**
* @var TokenStorageInterface
*/
private $tokenStorage:
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function __invoke()
{
// $token is an object of type Webauthn\Bundle\Security\Authentication\Token\WebauthnToken
$token = $this->tokenStorage->getToken();
...
}
}security:
firewalls:
main:
webauthn:
authentication:
profile: 'acme'security:
firewalls:
main:
webauthn:
registration:
enabled: truesecurity:
access_control:
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }fetch('/register/options', {
method : 'POST',
credentials : 'same-origin',
headers : {
'Content-Type' : 'application/json'
},
body: JSON.stringify({
"username": "[email protected]",
"displayName": "John Doe"
})
}).then(function (response) {
return response.json();
}).then(function (json) {
console.log(json);
}).catch(function (err) {
console.log({ 'status': 'failed', 'error': err });
})security:
firewalls:
main:
webauthn:
registration:
routes:
options_path: /security/registration/options
access_control:
- { path: ^/security/registration/options, roles: IS_AUTHENTICATED_ANONYMOUSLY}fetch('/register', {
method : 'POST',
credentials : 'same-origin',
headers : {
'Content-Type' : 'application/json'
},
body: //put the security device response here
}).then(function (response) {
return response.json();
}).then(function (json) {
console.log(json);
}).catch(function (err) {
console.log({ 'status': 'failed', 'error': err });
})security:
firewalls:
main:
webauthn:
registration:
routes:
result_path: /security/registration/resultsecurity:
firewalls:
main:
webauthn:
registration:
profile: 'acme'security:
access_control:
- { path: ^/admin, roles: [ROLE_ADMIN, IS_USER_VERIFIED]}security:
firewalls:
main:
webauthn:
options_storage: 'App\Handler\MyCustomRequestOptionsStorage'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'