Skip to content

feat(verifier): profile-free response verification helpers#877

Merged
Spomky merged 2 commits into5.4.xfrom
feat/verifier-helpers
May 5, 2026
Merged

feat(verifier): profile-free response verification helpers#877
Spomky merged 2 commits into5.4.xfrom
feat/verifier-helpers

Conversation

@Spomky
Copy link
Copy Markdown
Contributor

@Spomky Spomky commented May 5, 2026

Summary

Companion of #876 (WebauthnOptionsResponse) for the response side. Adds a single autowired entry point (WebauthnResponseVerifier) so applications can write their own response controllers without touching creation_profiles / request_profiles config or the bundle's profile-driven AttestationResponseController / AssertionResponseController.

$result = $this->verifier
    ->forAttestation('example.com')
    ->verify($request);
// $result->credentialRecord is already persisted
// $result->publicKeyCredential is the deserialised body (useful for Signal API)
// $result->userEntity is the user loaded from OptionsStorage

$result = $this->verifier
    ->forAssertion('example.com')
    ->verify($request);
// $result->credentialRecord has its counter / backup state updated

Design

  • Two typed verifiers: WebauthnAttestationVerifier and WebauthnAssertionVerifier (returned by the entry-point factories) so IDE autocomplete only proposes the methods relevant to each ceremony.
  • Auto-persist on attestation: the new credential record is saved through CanSaveCredentialRecord by default. Opt out with withSaveCredential(false) if your controller wants to own persistence (transactions, extra fields, etc.).
  • Auto conditional mediation: when stored creation options carry mediation: conditional, the verifier transparently switches to the conditional creation ceremony manager (relaxes UV per the W3C spec). One call site, both flows.
  • Failure split:
    • BadRequestHttpException for malformed inputs (wrong content type, empty body, deserialisation failure, unknown challenge, response/options type mismatch, RP ID mismatch). Symfony's HTTP layer turns these into HTTP 400 automatically.
    • WebauthnAuthenticationFailureException for ceremony validation failures, carrying the deserialised PublicKeyCredential, the underlying AuthenticatorResponse, the PublicKeyCredentialOptions and the user entity. Lets controllers build Signal API payloads (signalUnknownCredential, etc.) from a single exception.

What's new

  • Webauthn\Bundle\Service\AbstractWebauthnVerifier (template method: deserialise body, load stored options, delegate validation)
  • Webauthn\Bundle\Service\WebauthnAttestationVerifier
  • Webauthn\Bundle\Service\WebauthnAssertionVerifier
  • Webauthn\Bundle\Service\WebauthnResponseVerifier (autowired entry point)
  • Webauthn\Bundle\Service\WebauthnVerificationResult (typed return value)
  • 16 unit tests + reusable SaveableInMemoryCredentialRepository fixture

Drive-by

  • Drop a stale @phpstan-ignore-next-line in WebauthnSignalResponse that no longer matched on PHP 8.4.
  • Regenerate the PHPStan baseline against PHP 8.4 (the version CI runs).

Notes

The existing profile-driven controllers stay untouched and supported until 6.0; this is a strictly additive, opt-in API. Documentation PR is web-auth/doc#TBD (will link once opened).

Companion of WebauthnOptionsResponse for the verification side. Single
autowired entry point (WebauthnResponseVerifier) with forAttestation() /
forAssertion() factories returning typed verifiers; verify() returns a
WebauthnVerificationResult carrying the credential record, the deserialised
PublicKeyCredential and the user entity.

Auto-handles credential persistence on attestation (CanSaveCredentialRecord),
auto-switches to the conditional creation ceremony when stored options carry
mediation=conditional, and wraps validation failures in
WebauthnAuthenticationFailureException so controllers can build Signal API
payloads from the rich exception context. Pre-validation problems bubble up
as BadRequestHttpException for HTTP 400 handling by the Symfony layer.
@Spomky Spomky added the enhancement New feature or request label May 5, 2026
@Spomky Spomky self-assigned this May 5, 2026
…rTest

openssl_pkey_get_details() returns ec.x / ec.y as raw bigint bytes with
leading zeros stripped, so roughly one ECDSA key out of 256 has a 31-byte
coordinate. Ec2Key then rejects it with "Invalid length for x coordinate"
and the happyPath / tampered tests fail flakily on CI. Pad both coordinates
to 32 bytes before packing them into the COSE_Key.
@Spomky Spomky merged commit 94ccff7 into 5.4.x May 5, 2026
5 checks passed
@Spomky Spomky deleted the feat/verifier-helpers branch May 5, 2026 22:36
Spomky added a commit that referenced this pull request May 5, 2026
…881)

With the per-controller helpers (PR #876, #877, #880) and the per-verifier
origin override (PR #879, #880) all shipped, the bundle's profile-driven
configuration is fully redundant. Mark the relevant YAML nodes as deprecated
so users get a clear migration message at boot, with no behaviour change.

Deprecated YAML nodes (will be removed in 6.0):

* webauthn.creation_profiles      -> WebauthnOptionsResponse::forCreation()
* webauthn.request_profiles       -> WebauthnOptionsResponse::forRequest()
* webauthn.controllers            -> user-written controllers + WebauthnOptionsResponse + WebauthnResponseVerifier
* webauthn.client_override_policy -> ClientOverridePolicy built inline + ->withClientOverrides() on the helper
* webauthn.allowed_origins        -> WebauthnAttestationVerifier / WebauthnAssertionVerifier::withAllowedOrigins(...)
* webauthn.allow_subdomains       -> WebauthnAttestationVerifier / WebauthnAssertionVerifier::withAllowSubdomains(bool)

Single-origin apps that drop allowed_origins entirely keep the
W3C-recommended same-origin fallback (CheckOrigin against the request host),
which is the expected behaviour for that topology.

webauthn.passkey_endpoints stays alive for now: its helper companion is on a
separate branch and will arrive in a follow-up PR.

webauthn.metadata is intentionally left alone (small, contained, no
helper-vs-config duality).

No behavioural change: the deprecated nodes still work exactly as before.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant