# Fluxer WebAuthn Passkey Architecture: Cross-Platform Implementation Guide

> Explore Fluxer's three-layer WebAuthn passkey architecture enabling cross-platform support for macOS Windows and Linux. Unify your UI with this implementation guide.

- Repository: [Fluxer/fluxer](https://github.com/fluxerapp/fluxer)
- Tags: architecture
- Published: 2026-03-17

---

**Fluxer implements a three-layer WebAuthn architecture that unifies browser-based and desktop-native passkey flows through a shared abstraction layer, automatically selecting platform-specific providers for macOS, Windows, and Linux while maintaining identical UI code across web and Electron clients.**

The `fluxerapp/fluxer` repository delivers a comprehensive passkey authentication system that bridges web and desktop environments. This architecture leverages **SimpleWebAuthn** for browser compatibility while introducing a native abstraction layer for Electron-based desktop clients, enabling seamless biometric authentication across platforms without duplicating UI logic.

## Three-Layer Architecture Overview

Fluxer’s passkey implementation is organized into three distinct layers that communicate through standardized JSON interfaces:

- **Client Web Layer**: Handles browser-based registration and authentication using `@simplewebauthn/browser` via [`WebAuthnUtils.tsx`](https://github.com/fluxerapp/fluxer/blob/main/WebAuthnUtils.tsx)
- **Client Desktop Layer**: Provides OS-level WebAuthn access through Electron’s main process, supporting both generic native modules and macOS-specific addons
- **Server Layer**: Generates challenges, verifies responses, and manages credential storage through [`AuthMfaService.tsx`](https://github.com/fluxerapp/fluxer/blob/main/AuthMfaService.tsx)

This separation allows the same React components to execute passkey ceremonies regardless of whether the user accesses Fluxer through a browser or the desktop application.

## Client-Side Web Implementation

The web client implementation in [`fluxer_app/src/utils/WebAuthnUtils.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_app/src/utils/WebAuthnUtils.tsx) serves as the primary entry point for all passkey operations. It abstracts platform detection and delegates to the appropriate underlying implementation.

### Platform Detection and Support

The `assertWebAuthnSupported()` function checks for WebAuthn availability by detecting the runtime environment:

```typescript
// fluxer_app/src/utils/WebAuthnUtils.tsx
export async function assertWebAuthnSupported(): Promise<void> {
  if (Platform.isElectron) {
    const electronApi = getElectronAPI();
    const nativeSupported = electronApi && (await electronApi.passkeyIsSupported?.());
    if (nativeSupported) return;
  }
  if (browserSupportsWebAuthn()) return;
  throw new Error('WebAuthn is not supported in this environment.');
}

```

When running inside Electron, the function queries the native bridge via `passkeyIsSupported` before falling back to standard browser capability detection.

### Unified Registration and Authentication

The `performRegistration()` and `performAuthentication()` functions provide a single API for both platforms:

```typescript
export async function performRegistration(
  options: PublicKeyCredentialCreationOptionsJSON,
): Promise<RegistrationResponseJSON> {
  await assertWebAuthnSupported();
  if (Platform.isElectron) {
    const electronApi = getElectronAPI();
    const nativeSupported = electronApi && (await electronApi.passkeyIsSupported?.());
    if (nativeSupported && electronApi.passkeyRegister) {
      return electronApi.passkeyRegister(options);   // ← native bridge
    }
  }
  return await startRegistration({ optionsJSON: options }); // ← browser fallback
}

```

The `performAuthentication()` function follows an identical pattern, calling `passkeyAuthenticate` on the Electron API when available, otherwise invoking `startAuthentication` from SimpleWebAuthn.

## Electron Preload Bridge

The Electron preload script in [`fluxer_desktop/src/preload/index.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/preload/index.tsx) exposes the native passkey API to the renderer process through secure IPC channels:

```typescript
export const electronApi = {
  passkeyIsSupported: (): Promise<boolean> => 
    ipcRenderer.invoke('passkey-is-supported'),
  passkeyAuthenticate: (options: PublicKeyCredentialRequestOptionsJSON): Promise<AuthenticationResponseJSON> =>
    ipcRenderer.invoke('passkey-authenticate', options),
  passkeyRegister: (options: PublicKeyCredentialCreationOptionsJSON): Promise<RegistrationResponseJSON> =>
    ipcRenderer.invoke('passkey-register', options),
};

```

This bridge forwards all calls to the main process, ensuring that privileged OS operations remain isolated from the renderer while maintaining type safety through shared TypeScript definitions.

## Main Process Provider Strategy

The main process implementation in [`fluxer_desktop/src/main/IpcHandlers.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/main/IpcHandlers.tsx) implements a **PasskeyProvider** interface that dynamically selects the appropriate native implementation based on the operating system and build configuration.

### Provider Selection Logic

The `createPasskeyProvider()` function determines which implementation to instantiate:

```typescript
function createPasskeyProvider(): PasskeyProvider {
  const macAddon = loadMacWebAuthnAddon();
  if (macAddon) {
    return createMacPasskeyProvider(macAddon);   // macOS-specific addon
  }
  return createNativePasskeyProvider();          // generic native module
}

```

On macOS, the system attempts to load the `electron-webauthn-mac` addon for tighter integration with Touch ID and the Secure Enclave. On Windows and Linux, or when the macOS addon is unavailable, it falls back to the generic `@electron-webauthn/native` module.

### Generic Native Implementation

The `createNativePasskeyProvider()` function wraps the cross-platform native module:

```typescript
function createNativePasskeyProvider(): PasskeyProvider {
  return {
    isSupported: nativeIsSupported,
    authenticate: async (options) => {
      const cred = await nativeGet(convertRequestOptions(options));
      return buildAuthenticationResponse(cred);
    },
    register: async (options) => {
      const cred = await nativeCreate(convertCreationOptions(options));
      return buildRegistrationResponse(cred);
    },
  };
}

```

This provider uses the `nativeGet` and `nativeCreate` functions from `@electron-webauthn/native` to communicate with the OS-level WebAuthn APIs on Windows and Linux.

### macOS-Specific Optimization

The `createMacPasskeyProvider()` function implements a fallback strategy for macOS environments where the signed addon cannot be loaded:

```typescript
function createMacPasskeyProvider(addon: WebAuthnMacAddon): PasskeyProvider {
  const fallbackProvider = createNativePasskeyProvider();
  let useAddon = true;

  async function callWithFallback<T>(addonOp: () => Promise<T>, nativeOp: () => Promise<T>): Promise<T> {
    if (!useAddon) return nativeOp();
    try {
      return await addonOp();
    } catch (e) {
      if (isMissingApplicationIdentifierError(e)) {
        useAddon = false;
        return nativeOp();
      }
      throw e;
    }
  }

  return {
    isSupported: async () => (useAddon ? true : fallbackProvider.isSupported()),
    authenticate: (options) => callWithFallback(
      async () => {
        const cred = await addon.getCredential(convertMacRequestOptions(options));
        return buildAuthenticationResponseFromMac(cred);
      },
      () => fallbackProvider.authenticate(options),
    ),
    register: (options) => callWithFallback(
      async () => {
        const cred = await addon.createCredential(convertMacCreationOptions(options));
        return buildRegistrationResponseFromMac(cred);
      },
      () => fallbackProvider.register(options),
    ),
  };
}

```

If the addon throws a missing application identifier error—common in unsigned macOS builds—the provider disables the addon and transparently falls back to the generic native implementation for subsequent operations.

## Server-Side Verification

The server implementation in [`packages/api/src/auth/services/AuthMfaService.tsx`](https://github.com/fluxerapp/fluxer/blob/main/packages/api/src/auth/services/AuthMfaService.tsx) handles challenge generation, response verification, and credential persistence using `@simplewebauthn/server`.

### Registration Options Generation

The `generateWebAuthnRegistrationOptions()` method creates registration challenges and enforces credential limits:

```typescript
async generateWebAuthnRegistrationOptions(userId: UserID) {
  const user = await this.repository.findUniqueAssert(userId);
  const existing = await this.repository.listWebAuthnCredentials(userId);

  if (existing.length >= 10) throw new WebAuthnCredentialLimitReachedError();

  const rpName = Config.auth.passkeys.rpName;
  const rpID   = Config.auth.passkeys.rpId;

  const options = await generateRegistrationOptions({
    rpName,
    rpID,
    userID: new TextEncoder().encode(user.id.toString()),
    userName: user.username!,
    userDisplayName: user.username!,
    attestationType: 'none',
    excludeCredentials: existing.map(c => ({
      id: c.credentialId,
      transports: c.transports ? Array.from(c.transports) as any : undefined,
    })),
    authenticatorSelection: {
      residentKey: 'required',
      requireResidentKey: true,
      userVerification: 'required',
    },
  });

  await this.saveWebAuthnChallenge(options.challenge, { context: 'registration', userId });
  return options;
}

```

The service enforces a hard limit of **10 credentials per user** and configures the relying party (RP) using values from `Config.auth.passkeys`.

### Response Verification

The `verifyWebAuthnRegistration()` method validates client responses against stored challenges:

```typescript
async verifyWebAuthnRegistration(userId, response, expectedChallenge, name) {
  await this.consumeWebAuthnChallenge(expectedChallenge, 'registration', { userId });

  const rpID = Config.auth.passkeys.rpId;
  const expectedOrigin = Config.auth.passkeys.allowedOrigins;

  const verification = await verifyRegistrationResponse({
    response,
    expectedChallenge,
    expectedOrigin,
    expectedRPID: rpID,
  });

  if (!verification.verified || !verification.registrationInfo) {
    throw new InvalidWebAuthnCredentialError();
  }
  // Store credential, update authenticator types...
}

```

The server verifies the origin against `Config.auth.passkeys.allowedOrigins` and the RP ID against `Config.auth.passkeys.rpId`, ensuring that only legitimate clients can register credentials.

## Cross-Platform Usage Examples

The unified API allows identical React components to function across web and desktop environments.

### Registering a Passkey

```typescript
import { performRegistration } from '@app/utils/WebAuthnUtils';
import { generateWebAuthnRegistrationOptions } from '@app/api/auth';

async function registerPasskey() {
  const options = await generateWebAuthnRegistrationOptions();
  const credential = await performRegistration(options);
  await apiClient.post('/auth/webauthn/register', { credential });
}

```

### Authenticating with a Passkey

```typescript
import { performAuthentication } from '@app/utils/WebAuthnUtils';
import { generateWebAuthnAuthenticationOptions } from '@app/api/auth';

async function loginWithPasskey() {
  const options = await generateWebAuthnAuthenticationOptions();
  const assertion = await performAuthentication(options);
  const { token } = await apiClient.post('/auth/webauthn/authenticate', { assertion });
}

```

Both snippets execute identically whether running in Chrome, Safari, or the Fluxer desktop application, as the platform-specific logic is encapsulated within [`WebAuthnUtils.tsx`](https://github.com/fluxerapp/fluxer/blob/main/WebAuthnUtils.tsx).

## Summary

- **Fluxer implements a three-layer architecture** separating web client utilities, Electron native bridges, and server verification services.
- **Platform detection occurs at runtime** in [`WebAuthnUtils.tsx`](https://github.com/fluxerapp/fluxer/blob/main/WebAuthnUtils.tsx), automatically routing to the native Electron bridge when available or falling back to browser-based SimpleWebAuthn.
- **The main process dynamically selects providers**, using the `electron-webauthn-mac` addon on signed macOS builds for Touch ID integration, while Windows and Linux use the generic `@electron-webauthn/native` module.
- **Automatic fallback protects against addon failures**; if the macOS addon fails to load due to unsigned bundles or missing identifiers, the system transparently switches to the generic native provider.
- **Server-side enforcement includes a 10-credential limit per user**, challenge tracking, and strict origin/RP ID validation through [`AuthMfaService.tsx`](https://github.com/fluxerapp/fluxer/blob/main/AuthMfaService.tsx).

## Frequently Asked Questions

### How does Fluxer handle WebAuthn differently on macOS versus Windows?

On macOS, Fluxer attempts to load the `electron-webauthn-mac` addon for native Touch ID and Secure Enclave integration, while Windows and Linux rely on the generic `@electron-webauthn/native` module that interfaces with the standard OS WebAuthn APIs. Both implementations expose the same `PasskeyProvider` interface, ensuring consistent behavior across platforms.

### What happens if the macOS addon fails to load?

The `createMacPasskeyProvider()` function implements a fallback mechanism that catches missing application identifier errors—common in unsigned development builds—and disables the addon. Subsequent operations automatically route to the generic native provider without requiring code changes or user intervention.

### How many passkeys can a user register in Fluxer?

The server enforces a limit of **10 passkeys per user** through the `generateWebAuthnRegistrationOptions()` method in [`AuthMfaService.tsx`](https://github.com/fluxerapp/fluxer/blob/main/AuthMfaService.tsx), which throws a `WebAuthnCredentialLimitReachedError` when attempting to exceed this threshold.

### Can the same UI components work for both web and desktop clients?

Yes. The `performRegistration()` and `performAuthentication()` functions in [`WebAuthnUtils.tsx`](https://github.com/fluxerapp/fluxer/blob/main/WebAuthnUtils.tsx) abstract platform differences, allowing identical React components to execute passkey ceremonies in both browser and Electron environments without conditional rendering or platform-specific logic.