Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions migration/from-v5.x-to-v6.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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([
Expand All @@ -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.
Expand Down
29 changes: 29 additions & 0 deletions symfony-bundle/advanced-behaviors/client-override-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
38 changes: 36 additions & 2 deletions symfony-bundle/options-helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, array{enabled, allowed_values?}>` 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' => [
Expand All @@ -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.
Expand Down