stellify/ui

Components

st-passkey-register

A WebAuthn passkey registration component that handles the full passkey creation flow with your Laravel backend.

Basic Usage

Wrap a form with <st-passkey-register> and specify your API endpoints:

<st-passkey-register
  options-endpoint="/passkey/register/options"
  store-endpoint="/passkey/register"
>
  <form>
    <input type="text" name="name" placeholder="Passkey name">
    <button type="submit">Register Passkey</button>
    <p data-passkey-error hidden class="text-red-500"></p>
  </form>
</st-passkey-register>

How It Works

  1. User submits the form
  2. Component POSTs form data to options-endpoint to get WebAuthn options
  3. Browser prompts user to create a passkey (biometric/security key)
  4. Component POSTs the credential to store-endpoint
  5. On success, redirects to Location header or reloads the page

Attributes

Attribute Required Description
options-endpoint Yes URL that returns WebAuthn PublicKeyCredentialCreationOptions
store-endpoint Yes URL to POST the created credential for storage

Laravel Backend

Your Laravel backend needs two endpoints. Here's an example using webauthn-lib:

// routes/web.php
Route::post('/passkey/register/options', [PasskeyController::class, 'options']);
Route::post('/passkey/register', [PasskeyController::class, 'store']);

The options endpoint should return JSON matching the WebAuthn spec:

{
  "challenge": "base64url-encoded-challenge",
  "rp": { "name": "My App", "id": "example.com" },
  "user": {
    "id": "base64url-encoded-user-id",
    "name": "user@example.com",
    "displayName": "John Doe"
  },
  "pubKeyCredParams": [
    { "type": "public-key", "alg": -7 },
    { "type": "public-key", "alg": -257 }
  ],
  "timeout": 60000,
  "authenticatorSelection": {
    "residentKey": "required",
    "userVerification": "required"
  }
}

Error Display

Add an element with data-passkey-error to display errors:

<p data-passkey-error hidden class="text-sm text-red-500 mt-2"></p>

The component will populate this with error messages and remove the hidden attribute.

Events

Event Detail
st-passkey-register:start Registration flow started
st-passkey-register:success { response } - Server response after storing credential
st-passkey-register:error { stage, error } - Error with stage (options/create/store)
st-passkey-register:cancel User cancelled the passkey prompt
const register = document.querySelector('st-passkey-register')

register.addEventListener('st-passkey-register:success', (e) => {
  console.log('Passkey registered:', e.detail.response)
})

register.addEventListener('st-passkey-register:error', (e) => {
  console.error(`Error at ${e.detail.stage}:`, e.detail.error)
})

Loading State

The component sets data-loading="true" during registration and disables the submit button:

st-passkey-register[data-loading="true"] button[type="submit"] {
  opacity: 0.5;
  cursor: wait;
}

CSRF Protection

The component automatically includes the CSRF token from either:

  • <meta name="csrf-token" content="..."> in the document head
  • <input name="_token" value="..."> inside the form

Browser Support

WebAuthn is supported in all modern browsers. If the browser doesn't support passkeys, the component:

  • Displays "Your browser does not support passkeys" in the error element
  • Disables the submit button

Programmatic Registration

You can trigger registration programmatically:

const register = document.querySelector('st-passkey-register')
await register.register()