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

This example assumes you've already defined AppAuthSchema. If you want a complete setup from entity definitions through Axum mounting, start with the Axum integration guide.

use better_auth::plugins::PasskeyPlugin;

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

Configuration

OptionTypeDefaultDescription
rp_idStringbase_url hostRelying Party ID (your domain)
rp_nameString"Better Auth"Relying Party display name
originStringrequest Origin headerExpected origin for WebAuthn responses
challenge_ttl_secsi64300 (5 min)Challenge expiration in seconds

rp_id must be an effective domain of origin. This is enforced by webauthn-rs, so combinations like rp_id = "example.com" with origin = "https://other.test" are rejected during configuration.

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 WebAuthn registration response and stores the passkey

Authentication Flow

  1. Client calls GET /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 assertion, 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-optionsGETGet 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: 'GET'
});
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 } = await verifyRes.json();

Errors

StatusCondition
400Invalid or missing WebAuthn response data
400Invalid or expired challenge
401Not authenticated (for registration/list/delete)
403Passkey not found

On this page