Craft has no way to use a passkey from a native screen, so an app built with
renderer: native cannot offer passkey sign-in at all.
Why the web answer does not apply
Passkeys on the web are navigator.credentials.create() / .get(), which
@stacksjs/auth's passkey module wraps (startRegistration,
startAuthentication, browserSupportsWebAuthn). All of those need a browsing
context. An all-native app has no web view, and a native screen's JSContext has
no navigator.credentials — browserSupportsWebAuthn() is simply false there,
whatever the device can actually do.
iOS's own answer is ASAuthorizationPlatformPublicKeyCredentialProvider, driven
through ASAuthorizationController, which is a native API and has no JS
equivalent.
What is there now
CraftNativeActions.swift handles these modules:
Auth Background Biometric Brightness Browser Clipboard Database DeepLink Device
Dialog Files Haptics Health HeartRate KeepAwake Lifecycle Linking Location Music
Network Notification Push SecureStorage Share Speech StatusBar StorageAuth is only signInWithApple (CraftSignInWithApple), and biometric is
Face ID for unlocking something the app already holds. Neither is a passkey.
AuthenticationServices is already imported in CraftApp.swift, so the
framework is linked — it is the credential half that is missing.
What it would take
A capability — say passkeys, gated like the rest — over
ASAuthorizationPlatformPublicKeyCredentialProvider:
create(options)→ registration, from the server'sgenerateRegistrationOptions(challenge, rp id, user handle)get(options)→ authentication, fromgenerateAuthenticationOptions- something for whether this device can do it at all, so a screen can decide whether to offer it
Both are person-facing (the system sheet asks for Face ID), so they belong on the untimed path — with the caveat in craft-native/craft#352, since a sheet whose result never comes back is exactly what broke Apple Health.
The relying-party id has to match the app's webcredentials: associated
domain, so whatever shape the API takes should make it hard to pass an rp id
the entitlement does not cover.
The fallback, for the record
A passkey flow opened in Craft's in-app browser does work, because Safari
supports WebAuthn and the associated domain is the app's. So an app is not
hard-blocked today — it just has to leave its own native screens to sign in,
which is the thing renderer: native exists to avoid.
Context: hq.training is adding passkeys (stacksjs/hq.training#277) and is all-native on the phone, so it hits this immediately.