Repository navigation
Ops Passkeys
Users can sign in with a passkey instead of their password (Face ID, Touch ID, Windows Hello, a phone or a security key); the password always stays as the fallback. Design: ADR 0050.
Passkeys are bound to the address users open MowDeck at. Tell Identity that address:
| Setting | Compose (infra/compose/.env) |
Helm (config) |
Meaning |
|---|---|---|---|
MOWDECK_IDENTITY_PUBLIC_URL |
IDENTITY_PUBLIC_URL |
IDENTITY_PUBLIC_URL (empty: https://<ingress.host>) |
The address in the browser's address bar, e.g. https://mowdeck.example.org. Empty turns passkeys off. |
MOWDECK_IDENTITY_PASSKEY_RP_ID |
IDENTITY_PASSKEY_RP_ID |
IDENTITY_PASSKEY_RP_ID |
Optional. A parent domain of that host (e.g. example.org) when passkeys should also work on other subdomains. |
MOWDECK_IDENTITY_PASSKEY_RP_NAME |
not set | not set | Name shown next to the passkey (default MowDeck). |
Rules the browser enforces:
-
HTTPS. Passkeys work only in a secure context:
https://, orhttp://localhostfor development. Over plain HTTP in the LAN the button does not appear. A private CA is fine as long as the devices trust it (the same requirement as the refresh cookie, see Mobile apps). -
A host name, not an IP address.
https://192.168.1.20cannot carry passkeys; give the server a name (local DNS, Tailscale MagicDNS, a public domain). -
One address. A passkey made at
https://mowdeck.home.landoes not work athttps://mowdeck.tailnet.ts.net. Pick the address everybody uses. Changing the host later makes existing passkeys useless (users sign in with their password and add new ones). -
Exact origin. A port belongs to the origin:
https://mowdeck.example.org:8443is a different origin fromhttps://mowdeck.example.org. Set the URL exactly as users type it.
When the value is not usable (plain HTTP, an IP address, a wrong RP ID), Identity logs passkeys are off: MOWDECK_IDENTITY_PUBLIC_URL is not usable with the reason and keeps running with passwords only. GET /api/v1/identity/login-methods shows {"password": true, "passkey": true} once it works.
Restart Identity after changing the settings (Compose: docker compose up -d identity).
Compose's Traefik and the Helm -auth Ingress put POST /api/v1/identity/passkeys/session and POST /api/v1/identity/app-login/session under the login rate limit (10 per minute, burst 20 per client address). POST /api/v1/identity/passkeys/challenge (asked for by every sign-in page, for the autofill offer) and POST /api/v1/identity/app-login/start have the looser limit of refreshes (60 per minute, burst 30). Identity applies the same limits in-process.
The iOS and Android apps sign in with a passkey through the system browser (Safari, Chrome) and return to the app with a one-time code; this needs an app build that has MowDeckShell.openAuthSession (older builds show only the password form). Passkeys are added in a browser (Einstellungen, Konto, Passkeys); the app then uses them. Nothing else is configured for the apps: no Associated Domains, no Digital Asset Links.
Migration 0003 of Identity adds passkeys, webauthn_challenges, app_login_requests and app_login_codes; it runs with the usual identity-migrate job. Downgrading to 0002 deletes all passkeys (passwords stay).
| Symptom | Cause |
|---|---|
| No "Mit Passkey anmelden" button |
IDENTITY_PUBLIC_URL empty or unusable (see the log), the page is not served over HTTPS, or the browser has no WebAuthn |
| The browser says the passkey is not for this site, or creating fails | The address in the address bar differs from IDENTITY_PUBLIC_URL (host, port or scheme) |
| "Dieser Passkey wurde nicht akzeptiert" | The passkey was removed, belongs to another installation, or its counter went backwards (the audit log has the reason under session.login_failed) |
| In the app the browser opens but the app stays on the sign-in page | Tap "App öffnen"; or more than a minute passed (the code expired), sign in again |
| "Diese Anmeldeanfrage ist abgelaufen oder schon benutzt" in the browser | The browser page was opened more than 10 minutes ago, or from an old link: start again from the app |
Mirrored from docs/ops/passkeys.md at commit 5968113c667cb4a8b25f17c329e950f2eeb6af3b. Edit the file in the repository, not this wiki page.
Mirrored from the docs/ directory at commit 5968113c667cb4a8b25f17c329e950f2eeb6af3b. Edit the files in the repository, not this wiki.
MowDeck documentation
- Home
- Phases
- Architecture
-
Decisions (ADR)
- Architecture decisions
- ADR 0001: OpenFGA instead of self-built authorization
- ADR 0002: Self-built Identity service in FastAPI instead of Keycloak or Zitadel
- ADR 0003: SvelteKit for the web app
- ADR 0004: Monorepo with a uv workspace
- ADR 0005: AGPL-3.0 for services and apps, Apache-2.0 for libraries
- ADR 0006: PostgreSQL for all persistence, no SQLite
- ADR 0007: NATS JetStream as event bus
- ADR 0008: Traefik as gateway
- ADR 0009: Command and event envelopes, subject naming and de-duplication
- ADR 0010: Python 3.13 for all services and libraries
- ADR 0011: Separate GPL-3.0 package for the PyMammotion adapter
- ADR 0012: Mower control interface and safety rules
- ADR 0013: Device-Gateway registry, sharding and per-mower concurrency
- ADR 0014: Authorization model, tuple ownership and the mowdeck-authz client
- ADR 0015: Tokens, sessions and key rotation
- ADR 0016: Fleet commands and authorization per command kind
- ADR 0017: Telemetry storage and retention
- ADR 0018: Realtime WebSocket protocol
- ADR 0019: Mammotion adapter transports, manual drive and secrets
- ADR 0020: Web app architecture
- ADR 0021: Browser authentication flow
- ADR 0022: Notifications: rules, recipients and delivery
- ADR 0023: Map rendering and projection
- ADR 0024: Scheduler: recurrence, rain pause and command issuing
- ADR 0025: Home Assistant integration as a pure API client
- ADR 0026: Manual drive channel over the Realtime WebSocket
- ADR 0027: Health history and theft alarm
- ADR 0028: Entitlements: plans as data and enforcement
- ADR 0029: Helm chart and release pipeline
- ADR 0030: Mowing patterns as plug-ins and the Planner
- ADR 0031: Billing with a Stripe adapter
- ADR 0032: Live camera over Agora, joined directly by the browser
- ADR 0033: Vision-assisted recovery with human approval
- ADR 0034: Linked cloud accounts and device discovery
- ADR 0035: Server-side camera frames and automatic AI analysis on errors
- ADR 0036: Native app shells with Capacitor, loading the web app from the server
- ADR 0037: Camera panorama calibration in the browser, with the AI supplying points only
- ADR 0038: Dual link per mower and firmware updates
- ADR 0039: Spot mowing by MowDeck-driven paths
- ADR 0040: Link heartbeat for online status
- ADR 0041: Spot runs from the dock: undock, transit and return
- ADR 0042: Drive calibration
- ADR 0043: Turn styles
- ADR 0044: Drive view with camera
- ADR 0045: Camera relay
- ADR 0046: Device settings as capabilities
- ADR 0047: The RTK base station as a Fleet device
- ADR 0048: Maintenance commands
- ADR 0049: Manual driving over the Mammotion cloud
- ADR 0050: Passkeys and the app login hand-off
- ADR 0051: Sign in with GitHub
- ADR 0052: Sign in with Apple
- Development
- Operations
- Spikes
- Guides
- Original plan (German)