Better Auth in Rust

Passkey

WebAuthn / FIDO2 passwordless authentication with passkeys.

The PasskeyPlugin enables passwordless authentication using the WebAuthn standard. Users can register passkeys (biometric, security keys, etc.) and use them to sign in without a password.

Setup

use better_auth::plugins::PasskeyPlugin;

let auth = BetterAuth::new(config)
    .database(database)
    .plugin(
        PasskeyPlugin::new()
            .rp_id("example.com")
            .rp_name("My Application")
            .origin("https://example.com")
    )
    .build()
    .await?;

Configuration

OptionTypeDefaultDescription
rp_idString"localhost"Relying Party ID (your domain)
rp_nameString"Better Auth"Relying Party display name
originString"http://localhost:3000"Expected origin for WebAuthn responses
challenge_ttl_secsi64300 (5 min)Challenge expiration in seconds
allow_insecure_unverified_assertionboolfalseAllow simplified verification (dev only)

The current implementation uses simplified WebAuthn verification. For production, keep allow_insecure_unverified_assertion disabled and integrate a full FIDO2 library like webauthn-rs for complete attestation and assertion verification.

How It Works

Registration Flow

  1. Client calls GET /passkey/generate-register-options to get WebAuthn creation options
  2. Client uses the browser's navigator.credentials.create() API with the options
  3. Client sends the credential response to POST /passkey/verify-registration
  4. Server validates the challenge round-trip and stores the passkey

Authentication Flow

  1. Client calls POST /passkey/generate-authenticate-options to get assertion options
  2. Client uses the browser's navigator.credentials.get() API with the options
  3. Client sends the assertion response to POST /passkey/verify-authentication
  4. Server validates the challenge, looks up the passkey, and creates a session

API Endpoints

The Passkey plugin exposes 7 endpoints. For full request/response details, see the OpenAPI Reference.

EndpointMethodDescription
/passkey/generate-register-optionsGETGet WebAuthn creation options
/passkey/verify-registrationPOSTVerify and store a new passkey
/passkey/generate-authenticate-optionsPOSTGet WebAuthn assertion options
/passkey/verify-authenticationPOSTVerify passkey and create session
/passkey/list-user-passkeysGETList all passkeys for the user
/passkey/delete-passkeyPOSTDelete a passkey
/passkey/update-passkeyPOSTUpdate passkey name

Client-Side Integration

Registration Example

// 1. Get registration options from the server
const optionsRes = await fetch('/auth/passkey/generate-register-options', {
  headers: { 'Authorization': `Bearer ${sessionToken}` }
});
const options = await optionsRes.json();

// 2. Create credential using the browser API
const credential = await navigator.credentials.create({ publicKey: options });

// 3. Send the response back to the server
const verifyRes = await fetch('/auth/passkey/verify-registration', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${sessionToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    response: credential,
    name: 'My Device'
  })
});

Authentication Example

// 1. Get authentication options
const optionsRes = await fetch('/auth/passkey/generate-authenticate-options', {
  method: 'POST'
});
const options = await optionsRes.json();

// 2. Get assertion using the browser API
const assertion = await navigator.credentials.get({ publicKey: options });

// 3. Verify with the server
const verifyRes = await fetch('/auth/passkey/verify-authentication', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ response: assertion })
});
const { session, user } = await verifyRes.json();

Errors

StatusCondition
400Invalid or missing WebAuthn response data
400Invalid or expired challenge
401Not authenticated (for registration/list/delete)
404Passkey not found
501Full WebAuthn verification not enabled

On this page