Skip to content

feat(supabase_flutter): Android Restore Credentials on top of passkeys - #1824

Open
spydon wants to merge 2 commits into
mainfrom
lukasklingsbo/android-restore-credentials
Open

feat(supabase_flutter): Android Restore Credentials on top of passkeys#1824
spydon wants to merge 2 commits into
mainfrom
lukasklingsbo/android-restore-credentials

Conversation

@spydon

@spydon spydon commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Feature. Closes #1791.

What is the current behavior?

Google Play requires apps with sign-in to restore the signed in state on a new Android device through Credential Manager's Restore Credentials API from April 2027. The issue asks for a server-issued single-use restore token because storing the rotating refresh token is unsafe: a stale copy replayed from a backup trips refresh token reuse detection and revokes the whole session family.

The requested token already exists in a different shape. A restore key is a WebAuthn credential; Android documents that the server side is identical to passkeys. Supabase Auth ships passkey endpoints (/passkeys/registration/*, /passkeys/authentication/*) with userVerification: preferred, so a silently created and silently asserted restore key is accepted, and signing in with it opens a fresh session that is independent of the old device's refresh token chain. supabase_auth already exposes those endpoints through auth.passkey, but nothing connects them to Android's CreateRestoreCredentialRequest / GetRestoreCredentialOption contract, and the README says nothing about the requirement.

What is the new behavior?

supabase_flutter gains a thin layer next to the existing passkey helpers:

  • RestoreCredentialInterface with createRestoreCredential(requestJson) and getRestoreCredential(requestJson), mirroring the two androidx.credentials calls. Both sides speak WebAuthn JSON strings, which is what Credential Manager accepts and produces, so wiring a Credential Manager plugin or a platform channel to it is two one-line forwards. Like the passkey helpers, supabase_flutter takes no dependency on a specific plugin.
  • AuthClient.createRestoreKey(restoreCredential, {friendlyName}) runs the registration ceremony for the signed in user and then names the passkey (Android restore key by default) so restore keys can be told apart from user-created passkeys and hidden from a management screen, which Android's guidance asks for.
  • AuthClient.signInWithRestoreKey(restoreCredential, {captchaToken}) runs the authentication ceremony on the new device, persists the session and fires signedIn.
  • A short README pointer; the lifecycle rules (one key per app, delete the previous key before creating a new one, delete on sign out) are in the dartdoc, and the full guide belongs in the docs site later.
  • Symbols registered in sdk-compliance.yaml under the existing auth.passkey.register_passkey and auth.passkey.sign_in_with_passkey capabilities. No new canonical IDs.

Both helpers are @experimental because they build on the BETA passkey feature.

Additional context

Server-side limitations that a GoTrue change would remove, none of them blocking:

  • Restore keys are indistinguishable from passkeys on the server. The friendly name convention is a workaround; a credential kind would let list filter them out.
  • Orphaned restore keys (uninstall, clear data) are not cleaned up and count against max_passkeys_per_user (default 10). There is no TTL.

Not included: a native Android plugin inside supabase_flutter, for the same reason the passkey helpers do not bundle one. The passkeys example stays web-only, so it does not exercise the new helpers.

Testing: unit tests drive both helpers against a fake platform implementation and a mocked passkey server, checking the request sequence, the JSON handed to the platform, the credential posted back, the rename, error propagation and the session/event on sign in. The ceremony against a real device was not run.

Adds RestoreCredentialInterface and the createRestoreKey and
signInWithRestoreKey helpers so an app can meet Google Play's Restore
Credentials requirement with a Supabase passkey as the restore key.

Closes #1791
@spydon
spydon requested a review from a team as a code owner September 10, 2026 09:05
@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 38 seconds.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 1349d6ef-c563-4d96-880f-6f97acedef59

📥 Commits

Reviewing files that changed from the base of the PR and between 78ddf55 and 36ed6e4.

📒 Files selected for processing (5)
  • packages/supabase_flutter/README.md
  • packages/supabase_flutter/lib/src/supabase_restore_credential.dart
  • packages/supabase_flutter/lib/supabase_flutter.dart
  • packages/supabase_flutter/test/restore_credential_test.dart
  • sdk-compliance.yaml

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

Support Android Restore Credentials (Play "Zero-Tap Sign-In", required April 2027)

2 participants