WebAuthn Passwordless Authentication
Doggy implements WebAuthn standard authentication via web-auth/webauthn-symfony-bundle, supporting fingerprint, face ID, and security keys.
Overview
WebAuthn credentials are stored in src/Entity/Security/WebauthnCredential.php (table webauthn_credentials), linked to App\Entity\Organization\Employee (the user entity).
The credential repository implements PublicKeyCredentialSourceRepositoryInterface for full credential lifecycle management.
Configuration
yaml
# config/packages/webauthn.yaml
webauthn:
credential_repository: 'App\Repository\Security\WebauthnCredentialRepository'
user_repository: 'App\Repository\Organization\EmployeeRepository'
creation_profiles:
default:
rp:
name: '%env(RELYING_PARTY_NAME)%'
id: '%env(RELYING_PARTY_ID)%'
public_key_credential_parameters:
- -7 # ES256
- -257 # RS256
authenticator_selection_criteria:
resident_key: required
user_verification: required
request_profiles:
default:
rp_id: '%env(RELYING_PARTY_ID)%'
user_verification: required
allowed_origins: ['http://localhost:8000']Security Firewall
yaml
# config/packages/security.yaml
security:
firewalls:
main:
custom_authenticator: App\Security\AppCustomAuthenticator
webauthn:
success_handler: App\Security\WebauthnSuccessHandler
authentication:
enabled: true
routes:
options_path: '/login/webauthn/options'
result_path: '/login/webauthn/result'Credential Entity
php
// src/Entity/Security/WebauthnCredential.php
class WebauthnCredential
{
private string $publicKeyCredentialId;
private string $type; // 'public-key'
private array $transports; // ['usb', 'nfc', 'internal']
private string $attestationType;
private string $credentialPublicKey;
private string $userHandle; // Employee ID
private int $counter;
private ?string $deviceName; // e.g. "Chrome on Mac"
#[ORM\ManyToOne(targetEntity: Employee::class, inversedBy: 'passkeys')]
private ?Employee $employee = null;
}Login Flow
Register Passkey
- User logs in and navigates to security settings
- Request options from
/user/webauthn/register/options - Browser creates credential via WebAuthn API
- Result submitted to
/user/webauthn/register/result - Public key stored in
webauthn_credentialstable
Passwordless Login
- Enter username, click "Passkey Login"
- POST to
/login/webauthn/optionsfor challenge - Browser signs via WebAuthn API
- Result submitted to
/login/webauthn/result WebauthnSuccessHandlerprocesses success (updates device, timestamp)
Success Handler
App\Security\WebauthnSuccessHandler:
- Updates credential last-used timestamp
- Auto-identifies device name (User-Agent parsing)
- Checks first-login status, redirects to password setup
- Supports password reset flow
Credential Repository
App\Repository\Security\WebauthnCredentialRepository implements:
findOneByCredentialId(): Find credential by IDfindAllForUserEntity(): Get all credentials for a usersaveCredentialSource(): Create/update credentialparseUserAgent(): Device name recognition