Files
ResolveSpec/pkg/security/PASSKEY_QUICK_REFERENCE.md
Hein 2e7b3e7abd feat(security): add database-backed passkey provider
- Implement DatabasePasskeyProvider for WebAuthn/FIDO2 authentication.
- Add methods for registration, authentication, and credential management.
- Create unit tests for passkey provider functionalities.
- Enhance DatabaseAuthenticator to support passkey authentication.
2026-01-31 22:53:33 +02:00

5.9 KiB

Passkey Authentication Quick Reference

Overview

Passkey authentication (WebAuthn/FIDO2) is now integrated into the DatabaseAuthenticator. This provides passwordless authentication using biometrics, security keys, or device credentials.

Setup

Database Schema

Run the passkey SQL schema (in database_schema.sql):

  • Creates user_passkey_credentials table
  • Adds stored procedures for passkey operations

Go Code

// Create passkey provider
passkeyProvider := security.NewDatabasePasskeyProvider(db, 
    security.DatabasePasskeyProviderOptions{
        RPID:     "example.com",
        RPName:   "Example App",
        RPOrigin: "https://example.com",
        Timeout:  60000,
    })

// Create authenticator with passkey support
auth := security.NewDatabaseAuthenticatorWithOptions(db, 
    security.DatabaseAuthenticatorOptions{
        PasskeyProvider: passkeyProvider,
    })

// Or add passkey to existing authenticator
auth = security.NewDatabaseAuthenticator(db).WithPasskey(passkeyProvider)

Registration Flow

Backend - Step 1: Begin Registration

options, err := auth.BeginPasskeyRegistration(ctx, 
    security.PasskeyBeginRegistrationRequest{
        UserID:      1,
        Username:    "alice",
        DisplayName: "Alice Smith",
    })
// Send options to client as JSON

Frontend - Step 2: Create Credential

// Convert options from server
options.challenge = base64ToArrayBuffer(options.challenge);
options.user.id = base64ToArrayBuffer(options.user.id);

// Create credential
const credential = await navigator.credentials.create({
    publicKey: options
});

// Send credential back to server

Backend - Step 3: Complete Registration

credential, err := auth.CompletePasskeyRegistration(ctx, 
    security.PasskeyRegisterRequest{
        UserID:            1,
        Response:          clientResponse,
        ExpectedChallenge: storedChallenge,
        CredentialName:    "My iPhone",
    })

Authentication Flow

Backend - Step 1: Begin Authentication

options, err := auth.BeginPasskeyAuthentication(ctx,
    security.PasskeyBeginAuthenticationRequest{
        Username: "alice", // Optional for resident key
    })
// Send options to client as JSON

Frontend - Step 2: Get Credential

// Convert options from server
options.challenge = base64ToArrayBuffer(options.challenge);

// Get credential
const credential = await navigator.credentials.get({
    publicKey: options
});

// Send assertion back to server

Backend - Step 3: Complete Authentication

loginResponse, err := auth.LoginWithPasskey(ctx,
    security.PasskeyLoginRequest{
        Response:          clientAssertion,
        ExpectedChallenge: storedChallenge,
        Claims: map[string]any{
            "ip_address": "192.168.1.1",
            "user_agent": "Mozilla/5.0...",
        },
    })
// Returns session token and user info

Credential Management

List Credentials

credentials, err := auth.GetPasskeyCredentials(ctx, userID)

Update Credential Name

err := auth.UpdatePasskeyCredentialName(ctx, userID, credentialID, "New Name")

Delete Credential

err := auth.DeletePasskeyCredential(ctx, userID, credentialID)

HTTP Endpoints Example

POST /api/passkey/register/begin

Request: {user_id, username, display_name} Response: PasskeyRegistrationOptions

POST /api/passkey/register/complete

Request: {user_id, response, credential_name} Response: PasskeyCredential

POST /api/passkey/login/begin

Request: {username} (optional) Response: PasskeyAuthenticationOptions

POST /api/passkey/login/complete

Request: {response} Response: LoginResponse with session token

GET /api/passkey/credentials

Response: Array of PasskeyCredential

DELETE /api/passkey/credentials/{id}

Request: {credential_id} Response: 204 No Content

Database Stored Procedures

  • resolvespec_passkey_store_credential - Store new credential
  • resolvespec_passkey_get_credential - Get credential by ID
  • resolvespec_passkey_get_user_credentials - Get all user credentials
  • resolvespec_passkey_update_counter - Update sign counter (clone detection)
  • resolvespec_passkey_delete_credential - Delete credential
  • resolvespec_passkey_update_name - Update credential name
  • resolvespec_passkey_get_credentials_by_username - Get credentials for login

Security Features

  • Clone Detection: Sign counter validation detects credential cloning
  • Attestation Support: Stores attestation type (none, indirect, direct)
  • Transport Options: Tracks authenticator transports (usb, nfc, ble, internal)
  • Backup State: Tracks if credential is backed up/synced
  • User Verification: Supports preferred/required user verification

Important Notes

  1. WebAuthn Library: Current implementation is simplified. For production, use a proper WebAuthn library like github.com/go-webauthn/webauthn for full verification.

  2. Challenge Storage: Store challenges securely in session/cache. Never expose challenges to client beyond initial request.

  3. HTTPS Required: Passkeys only work over HTTPS (except localhost).

  4. Browser Support: Check browser compatibility for WebAuthn API.

  5. Relying Party ID: Must match your domain exactly.

Client-Side Helper Functions

function base64ToArrayBuffer(base64) {
    const binary = atob(base64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) {
        bytes[i] = binary.charCodeAt(i);
    }
    return bytes.buffer;
}

function arrayBufferToBase64(buffer) {
    const bytes = new Uint8Array(buffer);
    let binary = '';
    for (let i = 0; i < bytes.length; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary);
}

Testing

Run tests: go test -v ./pkg/security -run Passkey

All passkey functionality includes comprehensive tests using sqlmock.