madwizard / webauthn-bundle
Web Authentication Relying Party Symfony bundle
Installs: 16
Dependents: 0
Suggesters: 0
Security: 0
Stars: 9
Watchers: 1
Forks: 0
Open Issues: 0
Type:symfony-bundle
Requires
- php: ^7.1.3
- madwizard/webauthn: ^0.0.1
- symfony/config: ^3.4|^4.0
- symfony/dependency-injection: ^3.4|^4.0
- symfony/framework-bundle: ^3.4|^4.0
- twig/twig: ^2.5
Requires (Dev)
- roave/security-advisories: dev-master
- symfony/phpunit-bridge: >=4.1
- symfony/yaml: ^3.4|^4.0
This package is not auto-updated.
Last update: 2021-04-11 23:38:13 UTC
README
Work in progress - use for testing purposes only
Symfony bundle for madwizard/webauthn package.
Resources
Installation
Installation via composer:
composer require madwizard/webauthn-bundle:^0.0.1
Library reference
Automatically built reference documentation (for both this library and the main WebAuthn library):
https://madwizard-thomas.github.io/webauthn/
Configuration
Note: preliminary information
This is a work in progress bundle - any part can change at any time until a stable version is released
-
Enable bundle in
config/bundles.php
return [ // ... MadWizard\WebAuthnBundle\MadWizardWebAuthnBundle::class => ['all' => true], ];
-
Configure bundle in file
config/packages/webauthn.yaml
madwizard_webauthn: relying_party: # id: example.com name: My webauthn website origin: https://www.example.com # Origin should match the host of your website and should be secure # (either encrypted via https or a trusted host like `localhost`). credential_store: service_id: App\Security\CredentialStore # See steps below # challenge_length: 64
-
Write a class implementing
UserCredentialInterface
:<?php use MadWizard\WebAuthn\Credential\UserCredentialInterface; use MadWizard\WebAuthn\Format\ByteBuffer; use MadWizard\WebAuthn\Crypto\CoseKey; class UserCred implements UserCredentialInterface { public function getCredentialId(): string { // Return credential id (base64url encoded string) } public function getPublicKey(): CoseKey { // Return public key } public function getUserHandle(): ByteBuffer { // Return user handle of credential's owner } }
-
Implement
CredentialStoreInterface
in a service specified in the configuration undercredential_store.service_id
.<?php use MadWizard\WebAuthn\Credential\CredentialStoreInterface; use MadWizard\WebAuthn\Credential\UserCredentialInterface; use MadWizard\WebAuthn\Credential\CredentialRegistration; class CredentialStore implements CredentialStoreInterface { public function findCredential(string $credentialId): ?UserCredentialInterface { // find credential by credentialId. Return null if not found } public function registerCredential(CredentialRegistration $credential) { // Store credential represented by $credential } public function getSignatureCounter(string $credentialId): ?int { // Return signature counter } public function updateSignatureCounter(string $credentialId, int $counter): void { // Update signature counter for credential } }
Usage
Registering a credential
<?php use MadWizard\WebAuthn\Exception\WebAuthnException; use MadWizard\WebAuthn\Format\ByteBuffer; use MadWizard\WebAuthn\Server\Registration\RegistrationOptions; use MadWizard\WebAuthn\Server\UserIdentity; use MadWizard\WebAuthnBundle\Manager\WebAuthnManager; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; class RegistrationController extends Controller { public function register(Request $request, WebAuthnManager $manager) { $vars = []; $posted = $request->isMethod('POST'); $vars['posted'] = $posted; try { if (!$posted) { // Get user identity. Note that the userHandle should be a unique identifier for each user // (max 64 bytes). The WebAuthn specs recommend generating a random byte sequence for each // user. The code below is just for testing purposes! $user = new UserIdentity(ByteBuffer::fromHex('aabbccdd'), 'dummy', 'Dummy user'); // Setup options $options = new RegistrationOptions($user); // Get array with configuration for webauthn client $clientOptions = $manager->startRegistration($options); $vars['clientOptions'] = $clientOptions; } else { $result = $manager->finishRegistrationFromRequest($request); // Credential is now registered // For this demo, show credential ID via twig: $vars['credentialId'] = $result->getCredentialId(); } } catch(WebAuthnException $e) { // NOTE: do not pass exception messages to the client. The exception messages could contain // security sensitive information useful for attackers. $vars['error'] = "Registration failed"; } return $this->render('register.html.twig', $vars); } }
The example code uses the webauthn-ui npm package to do the client side processing.\
Base template modification:
{# Add in <head> of base.html.twig #} <script src="https://unpkg.com/webauthn-ui@0.0.2/dist/umd/webauthn-ui.min.js"></script>
Alternatively you can npm install webauthn-ui
and use your own bundler like webpack to include the script.
register.html.twig:
{% extends "base.html.twig" %} {% block body %} Register credential: {% if error is defined %} <p>{{ error }}</p> {% elseif posted %} <p>Registered credential with id : <code>{{ credentialId }}</code></p> {% else %} <p>Please perfom the user action your authenticator.</p> {{ webauthn_form(clientOptions) }} {% endif %} {% endblock %}
Authenticating with credentials (second factor mode)
<?php use MadWizard\WebAuthn\Exception\WebAuthnException; use MadWizard\WebAuthnBundle\Manager\WebAuthnManager; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use MadWizard\WebAuthn\Server\Authentication\AuthenticationOptions; class AuthenticationController extends Controller { public function authenticate(Request $request, WebAuthnManager $manager, CredentialStore $store) { $vars = []; $posted = $request->isMethod('POST'); $vars['posted'] = $posted; try { if (!$posted) { $options = new AuthenticationOptions(); // Specify which credentials are allowed to authenticate // Normally there can be multiple credentials for one user. This example just adds one. $credential = $store->findCredential('---DEMO CREDENTIAL ID HERE---'); // <!---- modify this $options->addAllowCredential($credential); // Get array with configuration for webauthn client $clientOptions = $manager->startAuthentication($options); $vars['clientOptions'] = $clientOptions; } else { $result = $manager->finishAuthenticationFromRequest($request); $vars['credentialId'] = $result->getUserCredential()->getCredentialId(); } } catch(WebAuthnException $e) { // NOTE: do not pass exception messages to the client. The exception messages could contain // security sensitive information useful for attackers. $vars['error'] = "Authentication failed"; } return $this->render('authentication.html.twig', $vars); } }
authentication.html.twig:
{% extends "base.html.twig" %} {% block body %} Authenticate with credential: {% if error is defined %} <p>{{ error }}</p> {% elseif posted %} <p>Authenticated as user: <code>{{ credentialId }}</code></p> {% else %} <p>Please perfom the user action your authenticator.</p> {{ webauthn_form(clientOptions) }} {% endif %} {% endblock %}