From 0e4512f7849e52fa9810e5f1f1c3bac08ccf8ebd Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Wed, 6 May 2026 09:17:40 +0200 Subject: [PATCH] Document the typed ClientOverrideRule + fromRules() factory --- migration/from-v5.x-to-v6.0.md | 17 +++++++-- .../client-override-policy.md | 29 ++++++++++++++ symfony-bundle/options-helpers.md | 38 ++++++++++++++++++- 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/migration/from-v5.x-to-v6.0.md b/migration/from-v5.x-to-v6.0.md index bc2eb89..7b38b26 100644 --- a/migration/from-v5.x-to-v6.0.md +++ b/migration/from-v5.x-to-v6.0.md @@ -110,7 +110,7 @@ The full refactored example is on the [Verification Helpers](../symfony-bundle/v #### `webauthn.client_override_policy` -Build a `ClientOverridePolicy` inline in the controller and attach it to the helper. +Build a `ClientOverridePolicy` inline in the controller and attach it to the helper. 5.4 ships a typed `ClientOverrideRule` value object that makes the call site much clearer than the legacy nested-array form (both shapes stay supported as first-class APIs): ```yaml # Before (deprecated) @@ -122,9 +122,20 @@ webauthn: ``` ```php -// After +// After (5.4) — typed factory recommended use Webauthn\Bundle\Policy\ClientOverridePolicy; +use Webauthn\Bundle\Policy\ClientOverrideRule; + +return $this->options + ->forRequest('example.com') + ->withClientOverrides(ClientOverridePolicy::fromRules( + userVerification: ClientOverrideRule::restrictTo(['preferred', 'required']), + )) + ->build($request); +``` +```php +// Or the legacy nested-array form, also first-class return $this->options ->forRequest('example.com') ->withClientOverrides(new ClientOverridePolicy([ @@ -136,8 +147,6 @@ return $this->options ->build($request); ``` -The `ClientOverridePolicy` constructor will be redesigned around typed value objects in 6.0; the YAML shape goes away with the deprecation. - #### `webauthn.allowed_origins` / `webauthn.allow_subdomains` Two migration paths depending on your topology. diff --git a/symfony-bundle/advanced-behaviors/client-override-policy.md b/symfony-bundle/advanced-behaviors/client-override-policy.md index 9b60aaa..95742f7 100644 --- a/symfony-bundle/advanced-behaviors/client-override-policy.md +++ b/symfony-bundle/advanced-behaviors/client-override-policy.md @@ -6,6 +6,10 @@ The Client Override Policy provides granular control over which WebAuthn options clients can override via request parameters. This allows you to define strict server-side defaults while optionally allowing clients to customize specific fields within constrained boundaries. +{% hint style="warning" %} +**5.4 helper-side API recommended.** When you build options through the [Options Helpers](../options-helpers.md), attach the policy with `withClientOverrides(...)` and use the typed `ClientOverridePolicy::fromRules()` factory documented under *Client overrides* on that page. The YAML `creation_profiles[].client_override_policy` form below stays supported but is deprecated alongside `creation_profiles` in 5.4. +{% endhint %} + ## Overview When building WebAuthn options from a profile, the server uses configured defaults. With the Client Override Policy, you can control whether HTTP request parameters can override these defaults and which values are acceptable. @@ -14,6 +18,31 @@ Each policy field has: * **enabled**: Whether the client can override this field at all * **allowed_values**: An optional list of accepted values (if omitted, all valid values are allowed) +## Helper-side, typed API (5.4) + +`ClientOverridePolicy::fromRules()` and `ClientOverrideRule` give you a named-argument, typed alternative to the YAML / nested-array form. Both shapes are first-class — pick whichever fits your use case best. + +```php +use Webauthn\Bundle\Policy\ClientOverridePolicy; +use Webauthn\Bundle\Policy\ClientOverrideRule; + +$policy = ClientOverridePolicy::fromRules( + userVerification: ClientOverrideRule::restrictTo(['preferred', 'required']), + authenticatorAttachment: ClientOverrideRule::restrictTo(['platform', 'cross-platform']), + residentKey: ClientOverrideRule::any(), + extensions: ClientOverrideRule::any(), +); + +return $this->options + ->forCreation('example.com', $this->guesser) + ->withClientOverrides($policy) + ->build($request); +``` + +* Pass `null` (the default) for fields the client must NOT be able to override. +* `ClientOverrideRule::any()` accepts any value the client submits. +* `ClientOverrideRule::restrictTo($allowedValues)` restricts to a list. + ## Configuration The example below shows every field with an explicit value. You only need to declare the fields you want to change — the rest fall back to the [defaults](#configurable-fields) listed in the next section. diff --git a/symfony-bundle/options-helpers.md b/symfony-bundle/options-helpers.md index 509507c..d6da790 100644 --- a/symfony-bundle/options-helpers.md +++ b/symfony-bundle/options-helpers.md @@ -181,12 +181,42 @@ Or swap with a custom implementation of `Webauthn\FakeCredentialGenerator` (e.g. ## Client overrides -By default, **anything in the client request body is ignored**: the server alone decides every field. To let the client influence specific fields, attach a `Webauthn\Bundle\Policy\ClientOverridePolicy`: +By default, **anything in the client request body is ignored**: the server alone decides every field. To let the client influence specific fields, attach a `Webauthn\Bundle\Policy\ClientOverridePolicy`. Two equivalent build paths are supported as first-class APIs. + +### Typed factory (recommended) + +Build the policy with named arguments and `ClientOverrideRule` value objects: + +{% code lineNumbers="true" %} +```php +use Webauthn\Bundle\Policy\ClientOverridePolicy; +use Webauthn\Bundle\Policy\ClientOverrideRule; + +return $this->options + ->forRequest('example.com') + ->withUser($user) + ->withUserVerification(AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED) + ->withClientOverrides(ClientOverridePolicy::fromRules( + userVerification: ClientOverrideRule::restrictTo(['preferred', 'required']), + extensions: ClientOverrideRule::any(), + )) + ->build($request); +``` +{% endcode %} + +* `ClientOverrideRule::any()` — accepts any value the client submits +* `ClientOverrideRule::restrictTo($allowedValues)` — restricts the client value to a list +* Pass `null` (the default) for fields the client must NOT be able to override + +### Nested-array form + +The legacy `array` shape stays supported as a first-class API; it can be useful when the policy is loaded from a config file or a database row: {% code lineNumbers="true" %} ```php return $this->options - ->forRequest('example.com', $user) + ->forRequest('example.com') + ->withUser($user) ->withUserVerification(AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED) ->withClientOverrides(new ClientOverridePolicy([ 'user_verification' => [ @@ -198,6 +228,10 @@ return $this->options ``` {% endcode %} +The constructor also accepts a mix of typed `ClientOverrideRule` entries and legacy `{enabled, allowed_values?}` arrays in the same call. + +### Behaviour + If the client posts `{"userVerification": "required"}`, the merged options carry `required`. If it posts `"discouraged"`, the policy rejects it (not in the allow-list) and the default (`preferred`) wins. Anything outside the policy keys is ignored regardless of what the body contains. See [Client Override Policy](advanced-behaviors/client-override-policy.md) for the full list of overridable fields.