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:

  1. Symfony UX configured - Follow the official Symfony UX documentation

  2. WebAuthn Bundle installed - See Symfony Bundle Installation

Installation

Install the WebAuthn Stimulus Controller via Composer:

composer require web-auth/webauthn-stimulus

This command will automatically:

  • Install the PHP package

  • Register the Stimulus controller via Symfony Flex

  • Configure AssetMapper to import the necessary JavaScript files

No build step required! The package works with Symfony AssetMapper, so you don't need Node.js, npm, yarn, or any JavaScript build tools. The browser imports the JavaScript files directly.

Verify Installation

Check that the Stimulus controller is properly registered in your assets/controllers.json:

assets/controllers.json
{
    "controllers": {
        "@web-auth/webauthn-stimulus": {
            "enabled": true
        }
    }
}

What's Included

The package provides a Stimulus controller that handles:

  • 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

Basic Usage

Once installed, you can use the Stimulus controller in your Twig templates with two simple functions:

  1. stimulus_controller() - Attach the controller to your form

  2. stimulus_action() - Trigger WebAuthn operations on button clicks

Registration Form Example

templates/registration/register.html.twig
<form
    action="{{ path('app_register') }}"
    method="post"
    {{ stimulus_controller('@web-auth/webauthn-stimulus', {
        creationOptionsUrl: path('webauthn.controller.creation.creation.new_user'),
        creationResultField: 'input[name="attestation"]'
    }) }}
>
    <input type="text" name="username" required>
    <input type="hidden" name="attestation">

    <button
        type="submit"
        {{ stimulus_action('@web-auth/webauthn-stimulus', 'register') }}
    >
        Register
    </button>
</form>

Authentication Form Example

templates/security/login.html.twig
<form
    action="{{ path('app_login') }}"
    method="post"
    {{ stimulus_controller('@web-auth/webauthn-stimulus', {
        requestOptionsUrl: path('webauthn.controller.request.request.login'),
        requestResultField: 'input[name="assertion"]'
    }) }}
>
    <input type="text" name="username" autocomplete="username webauthn">
    <input type="hidden" name="assertion">

    <button
        type="submit"
        {{ stimulus_action('@web-auth/webauthn-stimulus', 'signin') }}
    >
        Sign In
    </button>
</form>

Controller Options

The Stimulus controller accepts various configuration options:

Registration Options

Option
Type
Required
Description

creationOptionsUrl

string

Yes

URL endpoint that returns PublicKeyCredentialCreationOptions

creationResultField

string

Yes

CSS selector for hidden field to store attestation response

Authentication Options

Option
Type
Required
Description

requestOptionsUrl

string

Yes

URL endpoint that returns PublicKeyCredentialRequestOptions

requestResultField

string

Yes

CSS selector for hidden field to store assertion response

useBrowserAutofill

boolean

No

Enable conditional UI for browser autofill (default: false)

Testing Your Installation

Create a simple test page to verify everything works:

templates/test/webauthn.html.twig
{% if app.user %}
    <p>Logged in as: {{ app.user.userIdentifier }}</p>
    <a href="{{ path('app_logout') }}">Logout</a>
{% else %}
    <h2>Register</h2>
    <form
        action="{{ path('app_register') }}"
        method="post"
        {{ stimulus_controller('@web-auth/webauthn-stimulus', {
            creationOptionsUrl: path('webauthn.controller.creation.creation.new_user'),
            creationResultField: 'input[name="attestation"]'
        }) }}
    >
        <label>
            Username:
            <input type="text" name="username" required>
        </label>
        <input type="hidden" name="attestation">

        <button
            type="submit"
            {{ stimulus_action('@web-auth/webauthn-stimulus', 'register') }}
        >
            Register with WebAuthn
        </button>
    </form>

    <h2>Login</h2>
    <form
        action="{{ path('app_login') }}"
        method="post"
        {{ stimulus_controller('@web-auth/webauthn-stimulus', {
            requestOptionsUrl: path('webauthn.controller.request.request.login'),
            requestResultField: 'input[name="assertion"]'
        }) }}
    >
        <label>
            Username:
            <input type="text" name="username" autocomplete="username webauthn">
        </label>
        <input type="hidden" name="assertion">

        <button
            type="submit"
            {{ stimulus_action('@web-auth/webauthn-stimulus', 'signin') }}
        >
            Sign In with WebAuthn
        </button>
    </form>
{% endif %}

Last updated

Was this helpful?