Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
composer require web-auth/webauthn-lib<?php
use Webauthn\PublicKeyCredentialRpEntity;
$rpEntity = new PublicKeyCredentialRpEntity(
'ACME Webauthn Server', // The application name
'acme.com' // The application ID = the domain
);https://domain.com:443sub.domain.com/indexhttps://user:[email protected]<?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;
use Webauthn\PublicKeyCredentialRpEntity;
$rpEntity = new PublicKeyCredentialRpEntity(
'Webauthn Server',
'my.domain.com'
);
$publicKeyCredentialSourceRepository = …; //Your repository here. Must implement Webauthn\PublicKeyCredentialSourceRepository
$server = new Server(
$rpEntity
$publicKeyCredentialSourceRepository
);<?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
);webauthn:
request_profiles:
acme: ~use Webauthn\Bundle\Service\PublicKeyCredentialCreationOptionsFactory;
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);
$publicKeyCredentialCreationOptions = $container
->get(PublicKeyCredentialCreationOptionsFactory::class)
->create('acme', $allowedCredentials)
;webauthn:
request_profiles:
acme:
rp_id: 'example.com'webauthn:
request_profiles:
acme:
challenge_length: 16webauthn:
creation_profiles:
acme:
timeout: 30000webauthn:
creation_profiles:
acme:
user_verification: !php/const Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIREDwebauthn:
creation_profiles:
acme:
extensions:
loc: true
txAuthSimple: 'Please add your new authenticator'<?php
public function registerBundles()
{
$bundles = [
// ...
new Webauthn\Bundle\WebauthnBundle(),
];
}webauthn:
credential_repository: 'App\Repository\PublicKeyCredentialSourceRepository'
user_repository: 'App\Repository\PublicKeyCredentialUserEntityRepository'const publicKey = "{PLACE YOUR CREDENTIAL OPTIONS HERE}";
function arrayToBase64String(a) {
return btoa(String.fromCharCode(...a));
}
function base64url2base64(input) {
input = input
.replace(/=/g, "")
.replace(/-/g, '+')
.replace(/_/g, '/');
const pad = input.length % 4;
if(pad) {
if(pad === 1) {
throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
}
input += new Array(5-pad).join('=');
}
return input;
}
publicKey.challenge = Uint8Array.from(window.atob(base64url2base64(publicKey.challenge)), function(c){return c.charCodeAt(0);});
publicKey.user.id = Uint8Array.from(window.atob(publicKey.user.id), function(c){return c.charCodeAt(0);});
if (publicKey.excludeCredentials) {
publicKey.excludeCredentials = publicKey.excludeCredentials.map(function(data) {
data.id = Uint8Array.from(window.atob(base64url2base64(data.id)), function(c){return c.charCodeAt(0);});
return data;
});
}
navigator.credentials.create({ 'publicKey': publicKey })
.then(function(data){
const publicKeyCredential = {
id: data.id,
type: data.type,
rawId: arrayToBase64String(new Uint8Array(data.rawId)),
response: {
clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)),
attestationObject: arrayToBase64String(new Uint8Array(data.response.attestationObject))
}
};
//Send the response to your server
// You can use JSON.stringify(publicKeyCredential); to have the JSON object as a string
})
.catch(function(error){
alert('Open your browser console!');
console.log('FAIL', error);
});webauthn:
creation_profiles:
acme: #Unique name of the profile
rp: # rp stands for Relying Party
name: 'ACME Webauthn Server'
id: 'acme.com'
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAAAwFBMVEXm7NK41k3w8fDv7+q01Tyy0zqv0DeqyjOszDWnxjClxC6iwCu11z6y1DvA2WbY4rCAmSXO3JZDTxOiwC3q7tyryzTs7uSqyi6tzTCmxSukwi9aaxkWGga+3FLv8Ozh6MTT36MrMwywyVBziSC01TbT5ZW9z3Xi6Mq2y2Xu8Oioxy7f572qxzvI33Tb6KvR35ilwTmvykiwzzvV36/G2IPw8O++02+btyepyDKvzzifvSmw0TmtzTbw8PAAAADx8fEC59dUAAAA50lEQVQYV13RaXPCIBAG4FiVqlhyX5o23vfVqUq6mvD//1XZJY5T9xPzzLuwgKXKslQvZSG+6UXgCnFePtBE7e/ivXP/nRvUUl7UqNclvO3rpLqofPDAD8xiu2pOntjamqRy/RqZxs81oeVzwpCwfyA8A+8mLKFku9XfI0YnSKXnSYZ7ahSII+AwrqoMmEFKriAeVrqGM4O4Z+ADZIhjg3R6LtMpWuW0ERs5zunKVHdnnnMLNQqaUS0kyKkjE1aE98b8y9x9JYHH8aZXFMKO6JFMEvhucj3Wj0kY2D92HlHbE/9Vk77mD6srRZqmVEAZAAAAAElFTkSuQmCC'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;
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!
}const publicKey = "{PLACE YOUR CREDENTIAL OPTIONS HERE}";
function arrayToBase64String(a) {
return btoa(String.fromCharCode(...a));
}
function base64url2base64(input) {
input = input
.replace(/-/g, '+')
.replace(/_/g, '/');
const pad = input.length % 4;
if(pad) {
if(pad === 1) {
throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
}
input += new Array(5-pad).join('=');
}
return input;
}
publicKey.challenge = Uint8Array.from(window.atob(base64url2base64(publicKey.challenge)), function(c){return c.charCodeAt(0);});
if (publicKey.allowCredentials) {
publicKey.allowCredentials = publicKey.allowCredentials.map(function(data) {
data.id = Uint8Array.from(window.atob(base64url2base64(data.id)), function(c){return c.charCodeAt(0);});
return data;
});
}
navigator.credentials.get({ 'publicKey': publicKey })
.then(function(data){
const publicKeyCredential = {
id: data.id,
type: data.type,
rawId: arrayToBase64String(new Uint8Array(data.rawId)),
response: {
authenticatorData: arrayToBase64String(new Uint8Array(data.response.authenticatorData)),
clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)),
signature: arrayToBase64String(new Uint8Array(data.response.signature)),
userHandle: data.response.userHandle ? arrayToBase64String(new Uint8Array(data.response.userHandle)) : null
}
};
//Send the response to your server
// You can use JSON.stringify(publicKeyCredential); to have the JSON object as a string
})
.catch(function(error){
alert('Open your browser console!');
console.log('FAIL', error);
});use Webauthn\Bundle\Service\PublicKeyCredentialCreationOptionsFactory;
use Webauthn\PublicKeyCredentialUserEntity;
$userEntity = new PublicKeyCredentialUserEntity(
'john.doe',
'ea4e7b55-d8d0-4c7e-bbfa-78ca96ec574c',
'John Doe'
);
$publicKeyCredentialCreationOptions = $container
->get(PublicKeyCredentialCreationOptionsFactory::class)
->create('acme', $userEntity)
;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'<?php
use Webauthn\PublicKeyCredentialRequestOptions;
$ublicKeyCredentialRequestOptions = $server->generatePublicKeyCredentialRequestOptions(
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED,
);use Webauthn\MetadataService\MetadataStatementRepository:
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Webauthn\MetadataService\SingleMetadata;
$myMetadataStatementRepository = new SimpleMetadataStatementRepository(
new FilesystemAdapter('webauthn') // We use filesystem caching in this example
);
// We add a local matadata statement (adapted from the Yubico website)
$myMetadataStatementRepository->addSingleStatement('yubico', new SingleMetadata(
'{"description": "Yubico U2F Root CA Serial 457200631","aaguid": "f8a011f3-8c0a-4d15-8006-17111f9edc7d","protocolFamily": "fido2","attestationRootCertificates": ["MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbwnebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXwLvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJhjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kthX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2kLVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1UsG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqcU9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw=="]}',
false // The statement is not base64 encoded
));<?php
use Webauthn\Server;
use Webauthn\PublicKeyCredentialRpEntity;
...
$server = new Server(
$rpEntity
$publicKeyCredentialSourceRepository,
$myMetadataStatementRepository // Inject your new service here
);webauthn:
metadata_service:
http_client: ... # An HTTP client for distant sources
request_factory: # PSR-17 request factory
services: # Services compatible with the MDS Specification
fido_alliance:
uri: 'https://mds2.fidoalliance.org'
additional_query_string_values:
token: '--ACCESS-TOKEN--' # We need to set the access token in the query string for this service
distant_single_statements:
solo: # A statement provided by Solo (https://solokeys.com/)
uri: 'https://raw.githubusercontent.com/solokeys/solo/2.1.0/metadata/Solo-FIDO2-CTAP2-Authenticator.json'
additional_headers: ~
from_data: # Single statements from local data
yubico:
data: '{"description": "Yubico U2F Root CA Serial 457200631","aaguid": "f8a011f3-8c0a-4d15-8006-17111f9edc7d","protocolFamily": "fido2","attestationRootCertificates": ["MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbwnebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXwLvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJhjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kthX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2kLVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1UsG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqcU9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw=="]}'<?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'<?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 these 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\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 optionnal and could be null
return new PublicKeyCredentialUserEntity(
$user->username,
$user->id,
$user->displayName,
$user->avatarUrl
);
}
}security:
firewalls:
main:
webauthn_json:
profile: 'acme'
http_message_factory: 'Nyholm\Psr7\Factory\Psr17Factory'security:
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: 'ROLE_ADMIN' }
- { path: ^/page, roles: 'ROLE_USER' }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }fetch('/login/options', {
method : 'POST',
credentials : 'same-origin',
headers : {
'Content-Type' : 'application/json'
},
body: JSON.stringify({
"username": "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_json:
options_path: /security/authentication/options
access_control:
- { path: ^/security, roles: IS_AUTHENTICATED_ANONYMOUSLY}fetch('/assertion/result', {
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_json:
login_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_json:
request_options_handler: 'App\Handler\MyCustomRequestOptionsHandler'security:
firewalls:
main:
webauthn_json:
success_handler: 'App\Handler\MyCustomAuthenticationSuccessHandler'security:
firewalls:
main:
webauthn_json:
failure_handler: 'App\Handler\MyCustomAuthenticationFailureHandler'security:
firewalls:
main:
webauthn_json:
request_options_storage: 'App\Handler\MyCustomRequestOptionsStorage'security:
access_control:
- { path: ^/admin, roles: IS_USER_VERIFIED}<?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);
/*
* 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 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;
/**
* @var \DateTimeImmutable
* @ORM\Column(type="datetime_immutable")
*/
private $createdAt;
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();
$this->createdAt = new \DateTimeImmutable();
parent::__construct($publicKeyCredentialId, $type, $transports, $attestationType, $trustPath, $aaguid, $credentialPublicKey, $userHandle, $counter);
}
public function getId(): string
{
return $this->id;
}
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
}
}<?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 App\Repository;
use App\Entity\PublicKeyCredentialSource;
use App\Entity\User;
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);
}
/**
* @return PublicKeyCredentialSource[]
*/
public function allForUser(User $user): array
{
$qb = $this->getEntityManager()->createQueryBuilder();
return $qb->select('c')
->from($this->getClass(), 'c')
->where('c.userHandle = :user_handle')
->setParameter(':user_handle', $user->getUserHandle())
->getQuery()
->execute()
;
}
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\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @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="string", length=255)
* @Assert\Length(max = 100)
*/
protected $name;
/**
* @ORM\Column(type="string", length=255)
* @Assert\Length(max = 100)
*/
protected $displayName;
/**
* @ORM\Column(type="array")
*/
protected $roles;
public function __construct(string $id, string $name, string $displayName, array $roles)
{
parent::__construct($name, $id, $displayName);
$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);
/*
* 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 App\Repository;
use App\Entity\User;
use Doctrine\Common\Persistence\ManagerRegistry;
use Webauthn\Bundle\Repository\AbstractPublicKeyCredentialUserEntityRepository;
use Webauthn\PublicKeyCredentialUserEntity;
final class PublicKeyCredentialUserEntityRepository extends AbstractPublicKeyCredentialUserEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function createUserEntity(string $username, string $displayName, ?string $icon): PublicKeyCredentialUserEntity
{
return new User($username, $displayName, [], $icon);
}
public function saveUserEntity(PublicKeyCredentialUserEntity $userEntity): void
{
if (!$userEntity instanceof User) {
$userEntity = $this->createUserEntity(
$userEntity->getName(),
$userEntity->getDisplayName(),
[],
$userEntity->getIcon()
);
}
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: ~