Repository navigation
feat(auth): sign in with a passkey, and manage passkeys in settings - #107
Merged
Merged
Conversation
Uses the passkey API added in server 1.7.0. The login screen offers "Sign in with a passkey" when /v1/auth/config reports passkeys_available, and Settings > Account gains a Passkeys screen to add (password, plus the 2FA or recovery code when 2FA is on), rename and remove them. The ceremony goes through androidx.credentials. Its Play Services provider is proprietary, so only the play flavour carries it; the open flavour gets passkeys on Android 14+, where the platform provider is built in. WebAuthn options and credentials are passed through as raw JSON. Moshi's Map decoding turns integers into doubles (alg -7 becomes -7.0), which the authenticator rejects.
The passkey screens had their own status-to-sentence table and read the error body with string(), which consumes it. Now that toUserMessage reads the server's detail and honours the technical-errors setting, failures go through it with preferDetail, since every passkey refusal comes with a detail that says what to do next. The step-up 401 keeps its prompt wording.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the client side of the passkey support that shipped in server 1.7.0.
What changes
GET /v1/auth/configreportspasskeys_available, the sign-in form gets a "Sign in with a passkey" button under the password one. It solves the login captcha first when the instance asks for one, runs the discoverable ceremony (no email), and hands the tokens to the same success path as a password login, so email verification, the watch handoff and push registration all behave the same. Backing out of the system sheet returns to the form with no error.usableis false says it was made under a previous address of the instance and needs re-adding.Distribution split
The ceremony goes through
androidx.credentials. On Android 14+ that uses the platform provider in the core library; below 14 it needscredentials-play-services-auth, which is proprietary. That dependency isplayImplementationonly, the same way FCM is handled, so:.play: passkeys from Android 9..open: passkeys on Android 14+. Older devices get "Passkeys aren't supported on this device" instead of a crash or a silent no-op.R8 keep rule for the Play Services provider added to
proguard-rules.pro(it is loaded by reflection).Raw JSON passthrough
The options from
beginand the credential sent tocompleteare opaque to the app and are carried byte for byte through a smallRawJsonMoshi adapter (nextSource/valueSink). Decoding them asMap<String, Any?>would turn every integer into a double, soalg: -7would reach the authenticator as-7.0and be rejected.Errors
Credential Manager failures map to short messages: no passkey on this device, this device already has one for the account, no provider. The
SecurityErrorcase, which is what a missing or wrongassetlinks.jsonproduces, says the operator needs to add the app toPASSKEY_ANDROID_APPS, because self-hosters are the people who will hit it and the platform's own message does not point there.Server failures go through
toUserMessage(preferDetail = true)from #106, since every passkey refusal (expired challenge, duplicate key, lockout, suspension) comes with a detail written for the person in front of it, and that also gets the technical-errors register for free. The step-up 401 in the add dialog keeps prompt wording ("Incorrect password", "Enter your two-factor code").Server-side requirements for testing
PASSKEYS_ENABLED=true(off by default in 1.7.0).PASSKEY_ANDROID_APPSlisting the package and signing fingerprint of the build under test. Debug builds are signed with the local debug keystore, so testing one needs that fingerprint added too (|-separated).Not in this PR
Verification
compileOpenDebugKotlin,compilePlayDebugKotlin, andminifyPlayReleaseWithR8/minifyOpenReleaseWithR8pass.testOpenDebugUnitTestpasses, including newRawJsonAdapterTest(integers survive the round trip; the credential is embedded as an object, not a string) andPasskeysViewModelTest(an instance without passkeys never lists them; a new key is sent with the device's credential and joins the list; cancelling the sheet is not an error and never callscomplete; a wrong password stays in the dialog; a 409 shows the server's own sentence).