wpconsulting / passkey-bundle
Symfony bundle for WebAuthn platform passkeys (Face ID, Touch ID, Samsung fingerprint, Windows Hello).
Package info
github.com/williamPeninon/passkey-bundle
Type:composer-plugin
pkg:composer/wpconsulting/passkey-bundle
Requires
- php: >=8.2
- composer-plugin-api: ^2.0
- doctrine/doctrine-bundle: ^2.11|^3.0
- doctrine/orm: ^2.17|^3.0
- lbuchs/webauthn: ^2.2
- symfony/console: ^6.4|^7.0
- symfony/framework-bundle: ^6.4|^7.0
- symfony/routing: ^6.4|^7.0
- symfony/security-bundle: ^6.4|^7.0
- symfony/stimulus-bundle: ^2.9
- symfony/translation-contracts: ^3.0
- symfony/twig-bundle: ^6.4|^7.0
- twig/twig: ^3.8
Requires (Dev)
- phpunit/phpunit: ^10.5|^11.5
- symfony/yaml: ^6.4|^7.0
This package is auto-updated.
Last update: 2026-07-27 19:20:41 UTC
README
Symfony bundle for WebAuthn / passkeys using the device’s platform authenticator:
| Platform | Method |
|---|---|
| Mac | Touch ID |
| iPhone / iPad | Face ID (or Touch ID) |
| Samsung / Android | Fingerprint (Credential Manager / Samsung Pass) |
| Windows | Windows Hello |
Unlocking the phone or Mac does not create a passkey for your site. Users must register a passkey from their logged-in account, on the same device and the same hostname (RP ID).
Requirements
- PHP
>= 8.2 - Symfony
^6.4or^7.0 - Doctrine ORM + Security + Twig + Stimulus (Asset Mapper / UX)
Quick install
composer require wpconsulting/passkey-bundle php bin/console passkey:configure
Then add the Twig partials (only remaining manual wiring):
Login
{% include '@Passkey/passkey/_login_button.html.twig' %}
Account (manage passkeys)
{% include '@Passkey/passkey/_manage.html.twig' with {
credentials: passkey_credentials(app.user)
} %}
What passkey:configure does
- publishes
config/packages/wp_consulting_passkey.yamlandconfig/routes/passkey.yaml - publishes
config/packages/dev/framework.yaml(trusted_proxiesfor ngrok — dev only) - registers the bundle in
config/bundles.phpif needed - asks for
user_class+user_identifier_field - implements
PasskeyUserInterfaceon the User entity - enables Stimulus controllers in
assets/controllers.json - adds
PUBLIC_ACCESSfor^/webauthn/login - creates the
web_authn_credentialtable (passkey:install/ migrations)
# Examples php bin/console passkey:configure --user-class=App\\Entity\\User php bin/console passkey:configure --no-db php bin/console passkey:configure -n # non-interactive if user_class is already in YAML
Flex (optional)
To also copy YAML via Symfony Flex:
{
"extra": {
"symfony": {
"endpoint": [
"https://raw.githubusercontent.com/williamPeninon/passkey-bundle/main/flex/index.json",
"flex://defaults"
]
}
}
}
Without a custom endpoint, Flex may show an auto-generated recipe — that is fine.
The Composer plugin then prints the post-install message (passkey:configure + details).
Allow it if Composer asks:
{
"config": {
"allow-plugins": {
"wpconsulting/passkey-bundle": true
}
}
}
Configuration
# config/packages/wp_consulting_passkey.yaml wp_consulting_passkey: user_class: App\Entity\User # entity FQCN, not a namespace user_identifier_field: email # Doctrine field used for login lookup rp_name: 'My App' login_authenticator: form_login default_redirect_route: app_account # success_handler: App\Security\LoginSuccessHandler translation_domain: PasskeyBundle translation_prefix: '' email_input_selector: '#username, input[name="_username"], input[name="email"], input[type="email"]'
| Option | Purpose |
|---|---|
user_class |
User entity (must implement PasskeyUserInterface) |
user_identifier_field |
Field used at WebAuthn login (email, username, …) |
rp_name |
Name shown in the passkey dialog |
login_authenticator |
Security authenticator passed to Security::login() |
default_redirect_route |
Redirect after biometric login |
success_handler |
Optional post-login handler |
email_input_selector |
CSS selectors for the identifier field on the login form |
Until user_class is a valid class implementing PasskeyUserInterface, business services are not wired: cache:clear / asset-map:compile still work. The passkey:configure command is always available.
User
use WpConsulting\PasskeyBundle\Contract\PasskeyUserInterface; class User implements UserInterface, PasswordAuthenticatedUserInterface, PasskeyUserInterface { public function getUserId(): mixed { return $this->id; } public function getUserName(): ?string { return $this->email; } public function getUserDisplayName(): string { return (string) $this->getUserName(); } }
Security / firewalls
passkey:configure adds:
access_control: - { path: ^/webauthn/login, roles: PUBLIC_ACCESS }
If you have a separate admin firewall, share the session context:
security: firewalls: main: { /* … */ } admin: pattern: ^/admin context: main
Otherwise the session created on /webauthn/* is invisible to admin → 401/403.
Database
php bin/console passkey:install
# or
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Check:
php bin/console doctrine:mapping:info
# → WpConsulting\PasskeyBundle\Entity\WebAuthnCredential
Twig
Login — _login_button.html.twig
{% include '@Passkey/passkey/_login_button.html.twig' with {
redirect_url: path('app_account'),
show_divider: true
} %}
Options: email_input, redirect_url, button_class, show_hint, show_divider, i18n messages…
Account — _manage.html.twig
{% include '@Passkey/passkey/_manage.html.twig' with {
credentials: passkey_credentials(app.user)
} %}
Options: add_button_class, wrapper_class, i18n labels…
Helpers
| Helper | Description |
|---|---|
passkey_credentials(user) |
List of passkeys |
passkey_manager |
PasskeyManager service |
passkey_redirect_path |
Post-login redirect URL |
passkey_email_input_selector |
Email CSS selectors |
passkey_translation_domain |
Translation domain |
CTA translations: PasskeyBundle (fr, en, es, de).
CSS
Bundled styles (assets/styles/passkey.css), loaded via Stimulus autoimport.
.passkey-manage { --pk-accent: #0f766e; --pk-ink: #1a2332; }
How it works
- Classic login (email / password).
- On the account page, Add Touch ID / Face ID / fingerprint.
- System dialog → public key stored in the DB (
web_authn_credential). - On
/login, the biometric button callsnavigator.credentials.getand authenticates the user.
| Device unlock | Site passkey | |
|---|---|---|
| Role | Opens the screen / Mac | Signs in to the app account |
| Where | System settings | Account page on the site |
| Tied to | The device | Account + hostname (RP ID) |
A passkey created on localhost does not work on xxx.ngrok-free.app or production (different RP ID): re-register on each hostname.
Platforms
Mac (Touch ID)
- Touch ID sensor + recent Safari / Chrome.
- Open the site at
http://localhost:…orhttps://localhost:…(not127.0.0.1). - No Face ID on Mac.
iPhone / iPad (Face ID)
- Safari (or Chrome iOS / WebKit).
- Same hostname as login; public HTTPS or ngrok tunnel without
--host-header=localhost.
Samsung / Android (fingerprint)
- Web passkeys = fingerprint (strong biometrics), not lock-screen face unlock.
- Chrome / Samsung Internet over HTTPS.
- “No biometrics” on login = no passkey registered for this account / device / hostname.
Windows Hello
- Fingerprint, PIN, or camera depending on hardware; Edge / Chrome.
| Device | UI label | Passkey method |
|---|---|---|
| Mac | Touch ID | Mac fingerprint |
| iPhone / iPad | Face ID | Face / Touch ID |
| Samsung | Samsung Fingerprint | Fingerprint |
| Android | Fingerprint | Fingerprint |
| Windows | Windows Hello | Hello |
Local dev & ngrok
- Forbidden:
127.0.0.1, bare IPs → invalid WebAuthn domain. - OK:
localhost, public HTTPS hostname. - ngrok: do not force
Host: localhost; the Host /X-Forwarded-Hostseen by Symfony must be the public hostname. - In dev,
passkey:configuremay publishconfig/packages/dev/framework.yaml(trusted_proxies). Do not reuse those values in prod — see SECURITY.md.
Web-only passkeys do not require a Google Cloud / OAuth project. Google OAuth or Digital Asset Links only apply to Google Sign-In / native apps linked to the site.
Commands
| Command | Purpose |
|---|---|
passkey:configure |
Full host-app wiring |
passkey:install |
Creates only the web_authn_credential table |
Migration from touch-id-bundle
Previous package: wpconsulting/touch-id-bundle (abandoned).
composer remove wpconsulting/touch-id-bundle composer require wpconsulting/passkey-bundle:^3.0 php bin/console passkey:configure
Update Twig includes (@Passkey/passkey/…) and the PasskeyUserInterface interface.
Details: CHANGELOG.md.
Security & contributing
- Reporting: SECURITY.md
- Changelog: CHANGELOG.md
- Issues: GitHub Issues
- Tests:
composer test
composer install
composer test
License
MIT © WP Consulting