Skip to content

feat: WebAuthn passkey authentication with IP-conditional remote login - #79

Open
mahdiwafy wants to merge 3 commits into
Vanszs:mainfrom
mahdiwafy:pr/passkey-auth
Open

feat: WebAuthn passkey authentication with IP-conditional remote login#79
mahdiwafy wants to merge 3 commits into
Vanszs:mainfrom
mahdiwafy:pr/passkey-auth

Conversation

@mahdiwafy

Copy link
Copy Markdown

Summary

Add WebAuthn passkey authentication support. Users can now register biometric (Touch ID, Face ID, Windows Hello) or security key passkeys for passwordless login, managed per-device via Google Password Manager, Apple iCloud Keychain, or any FIDO2-compatible credential manager.

What's Included

Database

  • New passkeys table in local SQLite schema (migration v5)
    • Stores credential ID, public key, counter, transports, device type, optional nickname

Backend — WebAuthn Ceremony API Routes

  • POST /api/auth/passkey/register/start — generate registration challenge (auth required)
  • POST /api/auth/passkey/register/finish — verify registration response, store credential
  • POST /api/auth/passkey/login/start — generate authentication challenge (public)
  • POST /api/auth/passkey/login/finish — verify assertion, issue JWT session cookie
  • GET|DELETE /api/auth/passkey/manage — list and delete registered passkeys

IP Detection + Conditional Auth

  • New setting remoteAuthMode: "password" (default), "passkey", or "both"
  • Uses the existing isLocalRequest() system — localhost always has full access
  • Remote users see auth methods based on remoteAuthMode:
    • "password": traditional password form (default, backward compatible)
    • "passkey": passkey-only login (password form hidden for remote users)
    • "both": both passkey button and password form shown
  • passkeysEnabled auto-flips to true when the first passkey is registered

UI Changes

  • Login page (/login): New "Sign in with Passkey" button when passkeys are available
  • Masuk page (/masuk): Same passkey support in Indonesian locale
  • Profile page (/dashboard/profile): Full passkey management section
    • Remote authentication mode selector (Password only / Passkey only / Both)
    • Register new passkey form with optional nickname
    • List of registered passkeys with device info, last-used date, and delete button

Settings

  • passkeysEnabled — auto-managed, set to true when first passkey registered
  • remoteAuthMode — controls remote login method availability

Architecture

  • Uses @simplewebauthn/server (v13) for server-side WebAuthn verification
  • Uses @simplewebauthn/browser (v13) for client-side credential creation/assertion
  • Passkeys stored securely in local SQLite (public key as base64url, never plaintext)
  • Session JWT cookie marked with passkey: true to track login method
  • RP ID and origin auto-detected from request headers (works with Tailscale funnel, custom domains, and localhost)

Usage

  1. Go to Settings → ProfilePasskey Authentication
  2. Click Add Passkey — your browser prompts you to create a passkey
  3. Set remote auth mode to "Passkey only" or "Password + Passkey"
  4. On next remote login, use biometrics / security key instead of typing a password

Testing

# Lint
npm run lint:undef

# Build
npm run build

# All routes compile — test by registering a passkey at /dashboard/profile

- New passkeys table & migration v5 (credential ID, publicKey, counter, etc.)
- WebAuthn ceremony API routes: register start/finish, login start/finish, manage
- IP detection via isLocalRequest: remoteAuthMode controls login options remotely
- Login page: passkey button when passkeys are available
- Masuk page: same passkey support in Indonesian locale
- Profile page: full passkey management (register, list, delete, remote mode selector)
- Uses @simplewebauthn/server + @simplewebauthn/browser v13
@mahdiwafy mahdiwafy closed this Jul 30, 2026
@mahdiwafy
mahdiwafy deleted the pr/passkey-auth branch July 30, 2026 05:06
@mahdiwafy
mahdiwafy restored the pr/passkey-auth branch July 30, 2026 05:16
@mahdiwafy mahdiwafy reopened this Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant