Hybrid WebAuthn is a cross-device authentication mechanism that allows users to authenticate on one device (e.g., computer) using an authenticator on another device (e.g., smartphone). This implementation provides maximum flexibility by supporting QR code/phone passkeys, security keys, and platform authenticators simultaneously.
In the WebAuthn specification, hybrid is one of several transport types:
usb- USB security keys (YubiKey, etc.)nfc- Near Field Communication authenticatorsble- Bluetooth Low Energy authenticatorsinternal- Platform authenticators (Touch ID, Face ID, Windows Hello)hybrid- Cross-device authentication
sequenceDiagram
participant Computer as Computer Browser
participant Server as WebAuthn Server
participant Phone as Phone/Mobile Device
Computer->>Server: Request registration/authentication
Server->>Computer: Return options with hybrid transport
Computer->>Computer: Display QR code + other options
Phone->>Computer: Scan QR code OR use proximity
Phone->>Phone: User authenticates (biometrics/PIN)
Phone->>Server: Send credential/assertion
Server->>Computer: Authentication complete
// Sources/MultiPeerChatCore/WebAuthnServer.swift
case "/webauthn/register/begin/hybrid":
response = try await handleHybridRegistrationBegin(request: request)// Sources/MultiPeerChatCore/WebAuthnServer.swift
case "/webauthn/authenticate/begin/hybrid":
response = try await handleHybridAuthenticationBegin(request: request)// Sources/MultiPeerChatCore/WebAuthnManager.swift
public func generateHybridRegistrationOptions(username: String) throws -> PublicKeyCredentialCreationOptions
public func generateHybridAuthenticationOptions(username: String? = nil) throws -> PublicKeyCredentialRequestOptions- File:
static/hybrid-webauthn.js - Purpose: Specialized client that always uses hybrid endpoints
- Usage: Test page and hybrid-specific implementations
- File:
static/hybrid-webauthn-test.html - URL:
http://localhost:8080/hybrid-webauthn-test.html - Purpose: Testing and demonstrating hybrid functionality
Android devices are now sandboxed from Linux code paths. Previously, Android's user agent (which contains "Linux") caused Android devices to be routed through Linux-specific code, bypassing biometric authenticators and credential providers.
// webauthn.js - Android excluded from Linux detection
isLinux() {
return navigator.userAgent.includes('Linux') && !navigator.userAgent.includes('Android');
}
isAndroid() {
return navigator.userAgent.includes('Android');
}- Endpoint:
POST /webauthn/register/begin/android - Discoverable Credentials:
residentKey: "preferred"for usernameless login - Credential Providers: No
authenticatorAttachmentrestriction, supports third-party providers - Biometrics:
userVerification: "preferred"delegates to the credential provider
// Restrictive - limits authenticator types
{
publicKey: {
authenticatorSelection: {
authenticatorAttachment: "platform", // Only Touch ID/Windows Hello
userVerification: "required"
}
}
}// Permissive - allows all authenticator types
{
publicKey: {
authenticatorSelection: {
authenticatorAttachment: undefined, // No restriction
userVerification: "preferred"
},
// Include all transport types
transports: ["usb", "nfc", "ble", "internal", "hybrid"]
}
}When hybrid options are provided, Chrome displays a dialog with multiple authentication methods:
-
π± "Use your phone"
- Shows QR code to scan
- Enables cross-device authentication
- Uses phone's biometrics/PIN
-
π "Use a security key"
- For USB/NFC hardware tokens
- YubiKey, FIDO2 keys, etc.
-
π» Platform Authenticators
- Touch ID (macOS)
- Windows Hello (Windows)
- Platform biometrics
Firefox may handle hybrid differently:
- May prioritize security keys over cross-device
- QR code support varies by version
- Generally good hardware key support
-
Start the server:
swift run ChatServer 8080
-
Navigate to test page:
http://localhost:8080/hybrid-webauthn-test.html -
Test Registration:
- Enter a username
- Click "Register with Hybrid WebAuthn"
- Chrome will show multiple options
- Choose your preferred method
-
Test Authentication:
- Enter username (optional for usernameless)
- Click "Authenticate with Hybrid WebAuthn"
- Use any previously registered authenticator
// Include the hybrid client
<script src="/static/hybrid-webauthn.js"></script>
// Initialize
const webauthn = new HybridWebAuthnClient();
// Register with hybrid options
const result = await webauthn.register(username, emoji, {
onStatus: (message, type) => console.log(message),
onSuccess: (data) => console.log('Success:', data),
onError: (error) => console.error('Error:', error)
});
// Authenticate with hybrid options
const authResult = await webauthn.authenticate(username, callbacks);// Registration
const response = await fetch('/webauthn/register/begin/hybrid', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username })
});
// Authentication
const authResponse = await fetch('/webauthn/authenticate/begin/hybrid', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username }) // Optional for usernameless
});// In WebAuthnManager.swift
public func generateHybridRegistrationOptions(username: String) throws -> PublicKeyCredentialCreationOptions {
var options = try generateUniversalRegistrationOptions(username: username)
// Remove authenticator attachment restrictions
options.authenticatorSelection?.authenticatorAttachment = nil
// Support all transport types including hybrid
options.allowCredentials?.forEach { credential in
credential.transports = ["usb", "nfc", "ble", "internal", "hybrid"]
}
return options
}// The hybrid client always uses hybrid endpoints
const endpoint = '/webauthn/register/begin/hybrid'; // Always hybrid
const authEndpoint = '/webauthn/authenticate/begin/hybrid'; // Always hybrid- Works with phones, security keys, and platform authenticators
- No single point of failure
- Users choose their preferred method
- Cross-device authentication is harder to intercept
- Phishing resistance maintained
- Multiple backup authentication methods
- One implementation supports all use cases
- Consistent behavior across different devices
- Fallback options if primary method fails
- Supports new authenticator types as they emerge
- Standards-compliant implementation
- Browser compatibility improvements benefit automatically
- Cause: Browser doesn't support hybrid transport
- Solution: Try Chrome 108+ or update browser
- Cause: USB/NFC transport not included
- Solution: Verify
transportsarray includes"usb"and"nfc"
- Cause: Touch ID/Windows Hello disabled or unsupported
- Solution: Enable platform authenticators in system settings
The test page provides detailed logging:
// Check browser support
console.log(`WebAuthn Support: ${webauthn.isSupported()}`);
console.log(`Browser: ${navigator.userAgent}`);
// Monitor registration options
console.log('Hybrid Server Options:', JSON.stringify(options, null, 2));- QR codes contain cryptographic challenges, not secrets
- Authentication happens on the phone, not via QR transmission
- Man-in-the-middle attacks are prevented by WebAuthn's cryptographic design
- All communications use HTTPS/secure contexts
- Cryptographic signatures verify authenticity
- No sensitive data transmitted in QR codes
- Cross-device authentication doesn't share device information
- User handles/IDs remain pseudonymous
- No tracking across devices
This implementation follows:
- WebAuthn Level 2 specification
- FIDO2/CTAP2 standards
- W3C Web Authentication API
- Browser-specific hybrid transport extensions
| Platform | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| QR Code/Hybrid | β 108+ | β No | β 108+ | |
| Security Keys | β | β | β 14+ | β |
| Platform Auth | β | β | β | β |
Legend: β
Full Support,
- Test the implementation using the hybrid test page
- Monitor browser compatibility as standards evolve
- Consider fallback strategies for unsupported browsers
- Implement logging to track authentication method preferences
- Update documentation as new features are added
For questions or issues, refer to the debug logs on the test page or check the server console output.