Repository navigation
feat(auth): register a passkey for the signed-in user - #649
Merged
Merged
Conversation
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.
Summary
A signed-in user can register a new passkey. The SDK fetches the WebAuthn creation challenge from the auth server and submits the credential the caller's platform authenticator created.
Capability:
auth.passkey.register_passkey, Register Passkey. No capability spec exists.Public API
New type in
Supabase.Gotrue.RegisterPasskey:The result reuses
Supabase.Gotrue.Common.Passkey.Both client members carry
[ImplementedCapability("auth.passkey.register_passkey")].Behavior
POST /passkeys/registration/optionswith the session bearer and no body.{challenge_id, options, expires_at}and maps it toPasskeyRegistrationChallenge.options(W3CPublicKeyCredentialCreationOptions, base64url binary fields, excluding the user's existing passkeys) is passed through untouched.expires_atis unix seconds, mapped toDateTimeOffset.GotrueException.POST /passkeys/registration/verifywith the session bearer, body{"challenge_id":"<uuid>","credential":<RegistrationResponseJSON verbatim>}.{id, friendly_name, created_at}, mapped toPasskeywithLastUsedAtnull.default(JsonElement)) throwsArgumentExceptionbefore any request.GotrueException.GotrueException. No newFailureHint.Reason; server codes surface onErrorCode.Reason = Offlinewhen the client is offline;Reason = NoSessionFoundwithout a session or access token. No request is sent in either case.no_authorizationfor anonymous users (route middleware), 422validation_failedfor SSO users, 403insufficient_aalfor MFA users below aal2, 422too_many_passkeys, 404passkey_disabled.webauthn_challenge_not_found(unknown or already used; consumed even by a failed attempt),webauthn_challenge_expired,webauthn_verification_failed, and 422webauthn_credential_exists.gotrue.start_passkey_registrationandgotrue.register_passkeyparent their HTTP spans.Contract source: supabase/auth
internal/api/passkey_registration.go(PasskeyRegistrationOptions,PasskeyRegistrationVerify,PasskeyMetadataResponse) andapi.go(route middleware). The authopenapi.yamldoes not describe/passkeys.Deviations from supabase-js
registerPasskey()drivesnavigator.credentials.create(). Anetstandard2.1library has no cross-platform WebAuthn API, so the SDK exposes the two server steps and the caller runs the ceremony: Blazor JS interop, Android Credential Manager,webauthn.dll, or a browser on a later request to an ASP.NET backend through theIGotrueApivariants. supabase-kt, supabase-flutter and supabase-swift (off Apple platforms) take the same split.Passkey(LastUsedAtnull) instead of a separate metadata-only type.JsonElementpassthrough; noAbortSignal.Guidchallenge id. Typed exceptions instead of{ data, error }.Tests
RegisterPasskeyTestsStartPasskeyRegistrationContractTestsRegisterPasskeyContractTestsArgumentExceptionand session checks before the wire. Cancellation, no context capture.RegisterPasskeyApprovalTestschallenge_idplus the credential verbatim for registration.OfflineClientContractTests,ObservabilityContractTestsThe E2E scenarios in
RegisterPasskeyTests:ListPasskeysAsync.webauthn_challenge_not_found.insufficient_aal.no_authorization.Changed-line coverage: 100% (44 lines). Mutation: 14/14 tested mutants on changed lines killed, 0 survivors (4 block-removal mutants on async bodies are ignored by Stryker).
Notes
Gotrue.Tests/Support/SoftwareAuthenticator.CreateCredential(JsonElement)answers creation options the SDK fetched and leaves the submission to the code under test. The existing seeding helpers are unchanged.Clone()on the options, as in feat(auth): sign in with a passkey #646: System.Text.Json reads aJsonElementproperty throughJsonElement.ParseValue, which returns an element that owns its data.config.tomlchange; passkeys and the WebAuthn relying party are already enabled (feat(auth): list the signed-in user's passkeys #634). Contract tests alone cover the following, because the local stack cannot produce them cheaply:too_many_passkeyswebauthn_credential_exists