A simple PHP WebAuthn (FIDO2) client library
webauthn-emulator
is a PHP library that emulates WebAuthn-compatible authenticators like YubiKeys, Touch ID, Face ID,
Windows Hello, etc. It essentially simulates the behavior
of Web Authentication API of a browser,
allowing the developers to integrate WebAuthn client-side authentication into their applications.
- Features
- Installation
- Quick Start
- Usage
- More Examples
- Storing Credentials
- Limitations
- Contributing and Reporting Issues
- License
- Acknowledgments
- Generate responses for WebAuthn registration (attestation) and authentication (assertion) requests.
- Works with any key storage.
- Supports multiple users and credentials.
The recommended way to install webauthn-emulator
is through Composer:
composer require pronin/webauthn-emulator
After installing webauthn-emulator
via Composer, you can quickly begin by creating an instance of the Authenticator
class and using it to handle WebAuthn registration and authentication.
Initialize the authenticator:
<?php
use WebauthnEmulator\Authenticator;
use WebauthnEmulator\CredentialRepository\FileRepository;
// Instantiate the file repository to store credentials
$storage = new FileRepository('path/to/credential/storage.txt');
// Create the authenticator instance
$authenticator = new Authenticator($storage);
Generate a response to a registration challenge:
// Sample registration challenge. See examples/webauthnio_reg.php for a complete example.
$registrationChallenge =
"rp" => [
"name" => "webauthn.io",
"id" => "webauthn.io"
],
"user" => [
"id" => "dGVzdDMxNA",
"name" => "test777",
"displayName" => "test777"
],
"challenge" => "7BYLAiLMeNm3103ZBmBIHxEI-5-O_5uWtkaNWC4oTzR47KtFLfs7oy0i0qCJ3A-ENpvsNMbdWbkHGvcFZyhBZQ",
"pubKeyCredParams" => [
[
"type" => "public-key",
"alg" => -7
]
],
"timeout" => 60000,
"excludeCredentials" => [],
"authenticatorSelection" => [
"residentKey" => "preferred",
"requireResidentKey" => false,
"userVerification" => "preferred"
],
"attestation" => "none",
"extensions" => [
"credProps" => true
]
];
// Generate a response to the registration challenge
$attestation = $authenticator->getAttestation($registrationChallenge);
Generate a response to an authentication challenge:
// Sample authentication challenge. See examples/webauthnio_login.php for a complete example.
$authChallenge = [
"challenge" => "RzckEwPCCFGmO-lkYs_z15YCKAsEcoW49X2DSuuCzL2b6iXjozuap5iVnWzenmfhbsTs0-mqKOwkvhbk8uDbRw",
"timeout" => 60000,
"rpId" => "webauthn.io",
"allowCredentials" => [
[
"id" => "2h0MoJD7Slojb_SecLOCfKyMDnC-mEDnFeYLTAefaz4",
"type" => "public-key",
"transports" => []
],
[
"id" => "ySHAlkz_D3-MTo2GZwXNRhDVdDLR23oQaSI3cGz-7Hc",
"type" => "public-key",
"transports" => []
]
],
"userVerification" => "preferred"
];
// Generate a response to the authentication challenge
$assertion = $authenticator->getAssertion(
$authChallenge['rpId'],
$authChallenge['allowCredentials'],
$authChallenge['challenge']
);
The webauthn-emulator
library provides a straightforward interface to emulate WebAuthn authenticators. Below are the
primary methods you will use to interact with the library, along with detailed explanations of their parameters.
To begin using the emulator, instantiate the Authenticator
class with a credential repository that implements
the RepositoryInterface
. The library includes a FileRepository
for testing purposes, which you can replace with a
custom repository for different storage solutions.
use WebauthnEmulator\Authenticator;
use WebauthnEmulator\CredentialRepository\FileRepository;
// Instantiate the file repository to store credentials
$storage = new FileRepository('path/to/credential/storage.txt');
// Create the authenticator instance
$authenticator = new Authenticator($storage);
The getAttestation
creates a new key pair and generates a response to a WebAuthn registration challenge, simulating
the process of registering a new authenticator with a WebAuthn relying party. Its behavior is similar to sequentially
calling navigator.credentials.create()
and navigator.credentials.get()
in the browser. Refer to
the Web Authentication API documentation for
more details.
getAttestation
accepts the following parameters:
registerOptions
(array, required): Contains the registration challenge data from the relying party, including the relying party's information, user data, challenge, and other registration options.origin
(string, optional): The origin of the relying party's website. It defaults to an origin constructed from therpId
if omitted.extra
(array, optional): Additional data to include in theclientDataJSON
object. If omitted, onlytype
,origin
, andchallenge
are included.
Returns an array similar to PublicKeyCredential,
$registrationChallenge = [
// ... (challenge data provided by the relying party)
];
// Generate a response to the registration challenge
$attestation = $authenticator->getAttestation(
registerOptions: $registrationChallenge,
origin: 'https://service.example.com', // optional
extra: ['crossOrigin' => false] // optional
);
echo(json_encode($attestation, JSON_PRETTY_PRINT));
/* Output:
{
"id": "HB_Pkygg...LQK3WkA",
"rawId": "HB\/Pkygg...LQK3WkA=",
"response": {
"clientDataJSON": "eyJ0e...pbyJ9",
"attestationObject": "o2Nmb...y4kw="
},
"type": "public-key"
}
*/
The getAssertion
method generates a response to a WebAuthn authentication challenge, simulating the process of logging
in with a previously registered key. Its behavior is similar to calling the
browser's navigator.credentials.get().
getAssertion
accepts the following parameters:
rpId
(string, required): The relying party identifier, typically the domain name of the relying party's website.credentialIds
(string|array|null, optional): A single credential ID, an array of credential descriptors, or null. It identifies which credentials are eligible for authentication. If null or omitted, any available credential for therpId
may be used.challenge
(string, required): A base64 or base64url encoded challenge from the relying party to prevent replay attacks.origin
(string, optional): The origin of the relying party's website. It defaults to an origin constructed from therpId
if omitted.extra
(array, optional): Additional data to include in theclientDataJSON
object. If omitted, onlytype
,origin
, andchallenge
are included.
Returns an array similar to PublicKeyCredential, the output of navigator.credentials.get() in the browser.
$authChallenge = [
// ... (challenge data provided by the relying party)
];
// Generate a response to the authentication challenge
$assertion = $authenticator->getAssertion(
rpId: $authChallenge['rpId'],
credentialIds: $authChallenge['allowCredentials'],
challenge: $authChallenge['challenge']
origin: 'https://service.example.com', // optional
extra: ['crossOrigin' => false] // optional
);
echo(json_encode($assertion, JSON_PRETTY_PRINT));
/* Output:
{
"id": "HB_Pkygg...LQK3WkA",
"rawId": "HB\/Pkygg...LQK3WkA=",
"response": {
"authenticatorData": "E4Mf1...AAA==",
"clientDataJSON": "eyJ0e...pbyJ9",
"signature": "MEYCI...lzwEj",
"userHandle": "ZmRmM...mZDk2"
},
"type": "public-key"
}
*/
WebAuthn servers often use base64url encoding to represent binary data in a URL-safe format. This encoding is similar to
standard base64 but uses different characters for padding and to represent the 62nd and 63rd values in the index table.
Specifically, base64url encoding replaces +
with -
, /
with _
, and omits the padding character =
. This makes it
suitable for use in URLs and filenames without requiring additional encoding.
Different WebAuthn server implementations vary in their use of base64url encoding. Some use base64url-encoded strings for 'id' or 'challenge' fields, while others use standard base64 encoding or a mix of both. This lack of consistency puts the burden to figure out which encoding is used by a particular server and to convert the data accordingly on the developer.
The webauthn-emulator
library provides two utility methods to handle these encoding variations:
-
base64Normal2Url
: Converts standard base64-encoded strings or arrays to base64url encoding. This method is useful when you need to send data to a server that expects base64url-encoded strings. -
base64Url2Normal
: Converts base64url-encoded strings or arrays back to standard base64 encoding with padding. This method is helpful when you receive data from a server that uses base64url encoding, before feeding it to the emulator.
These methods can be applied recursively to arrays, making it easy to encode or decode all elements within an array.
When interacting with a WebAuthn server, you may need to encode or decode the 'id', 'challenge', or other binary data fields. Here's how you can use the provided methods:
With single strings:
use WebauthnEmulator\Authenticator;
// Example of recoding a standard base64 string to base64url
echo Authenticator::base64Normal2Url('wib1OPW9EkDeiwUoyTgJ1+PpFG4dljeXodqRX15DG+gBAAAABQ==');
// Output: wib1OPW9EkDeiwUoyTgJ1-PpFG4dljeXodqRX15DG-gBAAAABQ
// Example of recoding a base64url string to standard base64
echo Authenticator::base64Url2Normal('wib1OPW9EkDeiwUoyTgJ1-PpFG4dljeXodqRX15DG-gBAAAABQ');
// Output: wib1OPW9EkDeiwUoyTgJ1+PpFG4dljeXodqRX15DG+gBAAAABQ==
With arrays:
use WebauthnEmulator\Authenticator;
$input = [
"id" => "HB_PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA", // already base64url-encoded
"rawId" => "HB/PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA=", // standard base64
];
// Example of recoding an array of standard base64 strings to base64url
$base64urlArray = Authenticator::base64Normal2Url($input);
/* Result:
[
"id" => "HB_PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA", // left as is
"rawId" => "HB_PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA", // recoded to base64url
]
*/
// Example of recoding an array of base64url to standard base64
$base64Array = Authenticator::base64Url2Normal($input);
/* Result:
[
"id" => "HB/PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA=", // recoded to standard base64
"rawId" => "HB/PkyggPmHCHbcYyQCfLXTakdmq3WGCcOBjLQK3WkA=", // left as is
]
*/
By using these methods, you can ensure that the data you send and receive from WebAuthn servers is correctly encoded, regardless of the server's specific implementation details.
For more detailed examples of how to use webauthn-emulator
to simulate WebAuthn registration and authentication
processes, refer to the examples directory in the repository:
- webauthnio_reg.php: Registration with a webauthn.io demo server.
- webauthnio_login.php: Login with a webauthn.io demo server.
- yubico_reg.php: Registration with a Yubico demo server (non-standard query structure).
- yubico_login.php: Login with a Yubico demo server (non-standard query structure).
- lubuch_reg.php: Registration with a Lubu.ch demo server (non-standard base64url/binary encoding).
- lubuch_login.php: Login with a Lubu.ch demo server (non-standard base64url/binary encoding).
- quadoio_reg.php: Registration with a Quado demo server (custom origins and additional data).
- quadoio_login.php: Login with a Quado demo server (custom origins and additional data).
Each server has its own peculiarities, so the examples demonstrate how to handle different scenarios, such as non-standard base64url encoding, custom origins, and additional data.
The webauthn-emulator
relies on a credential repository to store and manage credentials. By default, an example
implementation using a file-based repository (FileRepository
) is provided. However, you can implement your own
repository to store credentials in other places, such as a database by adhering to the RepositoryInterface
.
Here's an example of using the provided FileRepository
:
use WebauthnEmulator\CredentialRepository\FileRepository;
// Path to the JSON file that will store the credentials
$storagePath = 'path/to/credential/storage.txt';
// Create a new FileRepository instance
$storage = new FileRepository($storagePath);
To create a custom repository, implement the RepositoryInterface
:
use WebauthnEmulator\CredentialRepository\RepositoryInterface;
use WebauthnEmulator\CredentialInterface;
class CustomRepository implements RepositoryInterface {
// Implement the required methods
public function save(CredentialInterface $credential): static {
// Logic to save the credential
}
public function get(string $rpId): array {
// Logic to retrieve credentials by rpId
}
public function getById(string $rpId, string $id): CredentialInterface {
// Logic to retrieve a credential by rpId and id
}
}
Replace the FileRepository
with your custom repository when initializing the Authenticator
:
use WebauthnEmulator\Authenticator;
// Assuming $customStorage is an instance of your custom repository
$authenticator = new Authenticator($customStorage);
This flexibility allows you to integrate the webauthn-emulator
with various storage backends, such as databases or
cloud storage solutions, depending on your application's requirements.
webauthn-emulator
is designed to support the core functionalities required for WebAuthn registration and
authentication. However, there are some limitations to be aware of:
- The emulator currently supports only the 'none' attestation format. Other formats like 'packed', 'tpm', 'android-safetynet', etc., are not supported.
- The library is limited to the ES256 (alg: -7) signing algorithm for public key credentials. Other algorithms like RS256 or EdDSA are not currently supported. If you need support for other algorithms, please open an issue or submit a pull request.
Contributions are welcome! If you'd like to contribute or have found a bug, please submit a pull request or report an issue on the project's GitHub repository. When contributing, ensure your code follows the project's existing style for consistency.
For feature requests or bug reports, please check the GitHub Issues to see if it has already been reported. If not, create a new issue with a detailed description.
webauthn-emulator
is open-source software licensed under the MIT License. For more details, see the LICENSE
file in the repository.
Special thanks to Rayaz Sultanov (codeproger) for co-creating webauthn-emulator
.