Skip to content

[6.x] Frontend Passkeys#14453

Merged
jasonvarga merged 12 commits into6.xfrom
frontend-passkeys
Apr 9, 2026
Merged

[6.x] Frontend Passkeys#14453
jasonvarga merged 12 commits into6.xfrom
frontend-passkeys

Conversation

@duncanmcclean
Copy link
Copy Markdown
Member

@duncanmcclean duncanmcclean commented Apr 7, 2026

This pull request adds passkey support to the {{ user:login_form }} and adds various other tags for setting up and managing passkeys from the frontend.

Tags

{{ user:login_form }}

To support passkeys on your login page, you will need to include the helpers script on your page:

<script src="/vendor/statamic/frontend/js/helpers.js"></script>

Then, you may combine the JS helpers & variables from the tag to add passkey support.

{{ user:login_form }}
    <!-- ... -->

    <button type="button" id="passkey-login">Login with Passkey</button>

    <script>
    Statamic.$passkeys.configure({
        optionsUrl: '{{ passkey_options_url }}',
        verifyUrl: '{{ passkey_verify_url }}',
        onSuccess: (data) => window.location = data.redirect || '/',
        onError: (error) => alert(error.message)
    });

    document.getElementById('passkey-login').addEventListener('click', () => {
        Statamic.$passkeys.authenticate();
    });

    // Enable browser autofill for passkeys
    Statamic.$passkeys.initAutofill();
    </script>
{{ /user:login_form }}

Important

For browser autofill to work, your email input must have autocomplete="username webauthn".

{{ user:passkey_form }}

The passkey form allows you to create passkeys from the frontend.

{{ user:passkey_form }}
    <input type="text" id="passkey-name" placeholder="Passkey name (e.g., My Laptop)">
    <button type="button" id="create-passkey">Create Passkey</button>

    {{ if success }}
        <p>Passkey created successfully!</p>
    {{ /if }}

    {{ if errors }}
        <ul>
            {{ errors }}
                <li>{{ value }}</li>
            {{ /errors }}
        </ul>
    {{ /if }}

    <script>
        document.getElementById('create-passkey').addEventListener('click', () => {
            const name = document.getElementById('passkey-name').value || 'My Passkey';

            Statamic.$passkeys.register({
                optionsUrl: '{{ passkey_options_url }}',
                verifyUrl: '{{ passkey_verify_url }}',
                name: name,
                onSuccess: () => location.reload(),
                onError: (error) => alert(error.message)
            });
        });
    </script>
{{ /user:passkey_form }}

{{ user:passkeys }}

This tag allows you to loop through your passkeys.

{{ user:passkeys as="passkeys" }}
    {{ if passkeys }}
        <h3>Your Passkeys</h3>
        <ul>
            {{ passkeys }}
                <li>
                    <strong>{{ name }}</strong>
                    {{ if last_login }}
                        <span>Last used: {{ last_login format="M j, Y g:i A" }}</span>
                    {{ else }}
                        <span>Never used</span>
                    {{ /if }}

                    {{ user:delete_passkey_form :id="id" }}
                        <button type="submit" onclick="return confirm('Delete this passkey?')">
                            Delete
                        </button>
                    {{ /user:delete_passkey_form }}
                </li>
            {{ /passkeys }}
        </ul>
    {{ else }}
        <p>You haven't set up any passkeys yet.</p>
    {{ /if }}
{{ /user:passkeys }}

Variables:

  • id - Passkey identifier
  • name - User-defined passkey name
  • last_login - Last time the passkey was used
  • delete_url - URL to delete this passkey

{{ user:delete_passkey_form }}

This tag renders a form to delete a passkey. Most useful inside a {{ user:passkeys }} loop.

{{ user:delete_passkey_form :id="id" }}
    <button type="submit">Delete</button>
{{ /user:delete_passkey_form }}

Statamic.$passkeys JavaScript API

The frontend helpers script (/vendor/statamic/frontend/js/helpers.js) now exposes Statamic.$passkeys with:

  • configure(options) - Sets default options that will be merged into subsequent method calls.
  • authenticate(options) - Login with a passkey
  • register(options) - Register a new passkey
  • initAutofill(options) - Enable browser autofill for passkeys
  • cancel() - Cancel in-progress operations
  • supported - Browser support check

Docs PR: statamic/docs#1883

Copy link
Copy Markdown
Member

@jasonvarga jasonvarga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Via AI: Nice refactor pulling the shared logic into base frontend controllers. A couple of things to address and a couple of minor notes below.

@duncanmcclean duncanmcclean requested a review from jasonvarga April 8, 2026 15:08
jasonvarga and others added 7 commits April 9, 2026 13:03
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…y_url

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gin controller

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jasonvarga jasonvarga merged commit 8cd4799 into 6.x Apr 9, 2026
18 checks passed
@duncanmcclean duncanmcclean deleted the frontend-passkeys branch April 10, 2026 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants