Installation¶
The Symfony UX Initiative enables high-interaction applications directly from Twig templates without writing custom JavaScript. The WebAuthn Stimulus Controller makes implementing passwordless authentication as simple as adding a few Twig functions to your forms.
Prerequisites¶
Before installing the Stimulus Controller, you need:
- Symfony UX configured — follow the official Symfony UX documentation
- WebAuthn Bundle installed — see Symfony Bundle Installation
Installation¶
Since v5.3.0 the Stimulus controllers are published on npm as @web-auth/webauthn-stimulus. The npm package is the canonical and only maintained source going forward — the legacy web-auth/webauthn-stimulus Composer package is deprecated and will be removed in 6.0.0.
Danger
Do not declare this package in assets/controllers.json. That file is reserved for Symfony PHP UX packages. Symfony\UX\StimulusBundle resolves every entry against an installed Composer package, so adding @web-auth/webauthn-stimulus there throws Could not find package "web-auth/webauthn-stimulus" referred to from controllers.json as soon as the Composer wrapper is gone (the very thing we want). Register the controllers from your JavaScript code instead — see below.
1. Pin the package in your importmap¶
With Symfony AssetMapper:
With Webpack Encore, Vite, esbuild or any other bundler:
The package depends on @hotwired/stimulus and @simplewebauthn/browser, which are pulled in automatically by AssetMapper or your bundler.
2. Register the controllers in your Stimulus app¶
Import the controllers from the package entry point and register them on your Stimulus Application. Use the same ---separated identifiers that stimulus_controller('@web-auth/webauthn-stimulus/...') produces, so the existing Twig helpers keep working unchanged:
import { Application } from '@hotwired/stimulus';
import {
AuthenticationController,
RegistrationController,
WebauthnController,
} from '@web-auth/webauthn-stimulus';
const app = Application.start();
// Dedicated controllers (recommended since v5.3.0)
app.register('web-auth--webauthn-stimulus--authentication', AuthenticationController);
app.register('web-auth--webauthn-stimulus--registration', RegistrationController);
// Legacy combined controller — only register if you still rely on it
app.register('web-auth--webauthn-stimulus', WebauthnController);
Info
Why these identifiers? stimulus_controller('@vendor/pkg/ctrl') simply transforms the name to a flat string by stripping the leading @ and turning / into --. So stimulus_controller('@web-auth/webauthn-stimulus/authentication') renders data-controller="web-auth--webauthn-stimulus--authentication" — the same identifier you registered in JS. Existing Twig templates that use the @-prefixed form keep working without changes.
The exact location of your Stimulus bootstrap file depends on your project layout. With the default Symfony AssetMapper recipe it is assets/bootstrap.js or assets/app.js. Anywhere your Application.start() runs is fine.
Deprecated: install via Composer (web-auth/webauthn-stimulus)¶
Danger
This installation path is kept for backward compatibility only. New projects should use the npm package above.
This command installs the PHP wrapper, which used to register the Stimulus controllers via Symfony Flex and configure AssetMapper to import them from a vendored copy. As of v5.3.0 the same JavaScript ships on npm and the PHP wrapper no longer brings any added value — it is scheduled for removal in 6.0.0.
What's included¶
The package provides Stimulus controllers that handle:
- Registration flows — calls
navigator.credentials.create()for you - Authentication flows — calls
navigator.credentials.get()for you - Base64 encoding/decoding — automatically handles data conversion
- Error handling — gracefully handles common WebAuthn errors
- Browser autofill — supports conditional UI for passkey selection
- Conditional create (v5.3.0+) — enhanced conditional UI support for both registration and authentication
- PRF extension — built-in support for the Pseudo-Random Function extension
- TypeScript declarations (v5.3.3+): the package ships
.d.tsfiles alongside the controllers and declares them through thetypesentry of itspackage.json, so editors andtscknow the values, targets, actions and events of each controller without any extra@typespackage - CTAP 2.1 extensions (v5.4.0+) —
credBlobround-trips through the controllers transparently - WebAuthn L3 native helpers (v5.4.0+) —
PublicKeyCredential.parseCreationOptionsFromJSON(),parseRequestOptionsFromJSON()andtoJSON()are used when the user agent ships them, with a SimpleWebAuthn-based fallback otherwise - Client capabilities (v5.4.0+) —
PublicKeyCredential.getClientCapabilities()is feature-detected; the resulting map is exposed on thewebauthn:authentication:connectandwebauthn:registration:connectevents so apps can tailor their flow
Info
Three controllers ship in the package:
WebauthnController— combined controller (legacy, still supported)AuthenticationController— dedicated authentication controllerRegistrationController— dedicated registration controller
The dedicated controllers offer better separation of concerns, enhanced conditional UI support, and improved error handling. The combined controller remains available for backward compatibility.
Basic usage¶
Once the controllers are registered, you can use them in your Twig templates with the standard Symfony UX helpers:
stimulus_controller()— attaches a controller to your formstimulus_action()— triggers WebAuthn operations on button clicksstimulus_target()— marks an input as a controller target
The first argument is the package-prefixed name — it will resolve to the ---separated identifier you registered in step 2.
Registration form example¶
<form
action="{{ path('app_register') }}"
method="post"
{{ stimulus_controller('@web-auth/webauthn-stimulus/registration', {
optionsUrl: path('webauthn.controller.creation.request.new_user'),
resultUrl: path('app_register'),
submitViaForm: true
}) }}
>
<input
type="text"
name="username"
required
{{ stimulus_target('@web-auth/webauthn-stimulus/registration', 'username') }}
>
<input
type="hidden"
name="attestation"
{{ stimulus_target('@web-auth/webauthn-stimulus/registration', 'result') }}
>
<button
type="button"
{{ stimulus_action('@web-auth/webauthn-stimulus/registration', 'register') }}
>
Register
</button>
</form>
Authentication form example¶
<form
action="{{ path('app_login') }}"
method="post"
{{ stimulus_controller('@web-auth/webauthn-stimulus/authentication', {
optionsUrl: path('webauthn.controller.request.request.login'),
resultUrl: path('app_login'),
submitViaForm: true
}) }}
>
<input
type="text"
name="username"
autocomplete="username webauthn"
{{ stimulus_target('@web-auth/webauthn-stimulus/authentication', 'username') }}
>
<input
type="hidden"
name="assertion"
{{ stimulus_target('@web-auth/webauthn-stimulus/authentication', 'result') }}
>
<button
type="button"
{{ stimulus_action('@web-auth/webauthn-stimulus/authentication', 'authenticate') }}
>
Sign In
</button>
</form>
Controller options¶
The dedicated controllers accept the following options. See the User Authentication and User Registration pages for the full list of values, targets, actions and events.
Registration options¶
| Option | Type | Default | Description |
|---|---|---|---|
optionsUrl |
string | /registration/options |
URL to fetch creation options |
resultUrl |
string | /registration/verify |
URL to submit the attestation response |
submitViaForm |
boolean | false |
Submit via form instead of fetch |
successRedirectUri |
string | – | URL to redirect after successful registration |
autoRegister |
boolean | false |
Auto-start registration on page load |
Authentication options¶
| Option | Type | Default | Description |
|---|---|---|---|
optionsUrl |
string | /authentication/options |
URL to fetch assertion options |
resultUrl |
string | /authentication/verify |
URL to submit the assertion response |
submitViaForm |
boolean | false |
Submit via form instead of fetch |
successRedirectUri |
string | – | URL to redirect after successful auth |
conditionalUi |
boolean | false |
Enable conditional UI (autofill) |
Troubleshooting¶
Could not find package "web-auth/webauthn-stimulus" referred to from controllers.json¶
You added @web-auth/webauthn-stimulus to assets/controllers.json. Remove that entry. The npm package is not a Symfony UX PHP package — controllers.json is wrong here. Register the controllers from your JS bootstrap file instead, as shown in step 2 above.
Controller not found¶
If you see "application Error: Controller 'web-auth--webauthn-stimulus--authentication' is not registered" (or similar):
- Verify the identifier in
app.register('web-auth--webauthn-stimulus--authentication', ...)matches the rendereddata-controllerattribute. Rememberstimulus_controller('@web-auth/webauthn-stimulus/authentication')producesdata-controller="web-auth--webauthn-stimulus--authentication". - Make sure your bootstrap file is included by your importmap (
{{ importmap('app') }}in your base template). - Clear Symfony cache:
php bin/console cache:clear.
JavaScript errors¶
If you see console errors:
- Ensure you're using HTTPS (required for WebAuthn).
- Check browser console for specific error messages.
- Verify WebAuthn routes are properly configured in
config/routes/webauthn.yaml. - Ensure the controller is loaded by checking the browser's network tab.
HTTPS requirement¶
WebAuthn requires HTTPS to function. For development, use the Symfony CLI for automatic HTTPS:
For production, ensure HTTPS is properly configured on your web server.
Next steps¶
Now that the Stimulus Controller is installed, proceed to:
- User Registration — create registration forms
- User Authentication — implement login flows
- Additional Authenticators — allow users to register backup devices
See also¶
- Symfony UX Documentation — official UX guide
- Stimulus Documentation — Stimulus framework reference
- WebAuthn Bundle — backend configuration
- JavaScript Integration — manual JavaScript implementation