From 0c9d90051f76f739bf28828dcaa0fae82468c29a Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Wed, 17 Jun 2026 08:05:05 -0300 Subject: [PATCH] docs: Update documentation from swift SDK changes (#47024) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Updates docs based on stable releases in `supabase/supabase-swift`. ## Changes analyzed - **SDK**: swift - **Repo**: https://github.com/supabase/supabase-swift - **Stable tag range**: `v2.47.2...v2.48.0` - **Commits**: `716e0de6e7e0ec8eba54c6c4dfb8e980b3d51370...e5020ae5a1d01c46cc60fb5eb01157666d7214b6` ## Documentation updates ### `apps/docs/spec/supabase_swift_v2.yml` - Added `sign-in-with-passkey` entry — `signInWithPasskey(presentationAnchor:)` high-level helper (iOS 16+/macOS 13+, experimental) - Added `register-passkey` entry — `registerPasskey(presentationAnchor:)` high-level helper (iOS 16+/macOS 13+, experimental) - Added `passkey-api` group stub with 7 lower-level method entries: - `passkey-list` — `listPasskeys()` - `passkey-update` — `renamePasskey(id:friendlyName:)` - `passkey-delete` — `deletePasskey(id:)` - `passkey-start-registration` — `getPasskeyRegistrationOptions()` - `passkey-verify-registration` — `verifyPasskeyRegistration(challengeId:credentialResponse:)` - `passkey-start-authentication` — `getPasskeyAuthenticationOptions()` - `passkey-verify-authentication` — `verifyPasskeyAuthentication(challengeId:credentialResponse:)` - Updated `auth-mfa-api` overview notes to mention WebAuthn MFA (experimental) - Updated `mfa-enroll` notes and added `enrollWebAuthnFactor` example - Updated `mfa-verify` notes and added `verifyWebAuthnFactor` example ### `apps/docs/content/guides/auth/passkeys.mdx` - Added Swift tabs to: Enable in the client, Register a passkey, Sign in with a passkey, Two-step API, Manage passkeys - Updated SDK version note to include `supabase-swift` v2.48.0 - Updated reference links to include Swift All new Swift passkey APIs are gated behind `@_spi(Experimental) import Supabase`. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Summary by CodeRabbit ## Documentation * Expanded Swift SDK passkeys documentation to cover enabling, registering, signing in, managing, and Admin-related notes. * Added experimental Swift guidance for WebAuthn/passkeys, including updated minimum SDK version requirements. * Extended MFA to support WebAuthn/passkeys enrollment and verification (in addition to existing methods). * Added reference documentation for low-level passkey APIs to support custom authentication flows. ## Chores * Updated documentation linting spell-check allow list to include **visionOS**. --------- Co-authored-by: Claude --- apps/docs/content/guides/auth/passkeys.mdx | 115 ++++++++++- apps/docs/spec/supabase_swift_v2.yml | 225 ++++++++++++++++++++- supa-mdx-lint/Rule003Spelling.toml | 1 + 3 files changed, 331 insertions(+), 10 deletions(-) diff --git a/apps/docs/content/guides/auth/passkeys.mdx b/apps/docs/content/guides/auth/passkeys.mdx index 0b7c12ae2d5..d9d872bb3cd 100644 --- a/apps/docs/content/guides/auth/passkeys.mdx +++ b/apps/docs/content/guides/auth/passkeys.mdx @@ -14,7 +14,7 @@ Passkey support is experimental. The API may change without notice. You must exp -**Requires `@supabase/supabase-js` v2.105.0 and later, or `supabase_flutter` v2.15.0 and later.** Upgrade your client library to use passkey authentication. +**Requires `@supabase/supabase-js` v2.105.0 and later, `supabase_flutter` v2.15.0 and later, or `supabase-swift` v2.48.0 and later.** Upgrade your client library to use passkey authentication. @@ -135,6 +135,19 @@ final supabase = Supabase.instance.client; Platform setup that the library cannot do for you (Associated Domains on iOS/macOS, Digital Asset Links on Android, and including the [`passkeys`](https://pub.dev/packages/passkeys) web SDK in `index.html` on web) is documented in the `supabase_flutter` package README. + + + +The Swift SDK gates passkey support behind `@_spi(Experimental)`. Add this import to every file that uses passkey APIs: + +```swift +@_spi(Experimental) import Supabase +``` + +The `SupabaseClient` itself needs no extra configuration — the experimental SPI is enabled at the import site, not at client initialization. + +Platform setup the library cannot perform for you (Associated Domains entitlement and a relying-party server with HTTPS) must be configured in your Xcode project. Refer to [Apple's documentation on passkeys](https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys) for details. + @@ -180,6 +193,25 @@ try { } ``` + + + +Available on iOS 16+, macOS 13+, and visionOS 1+. Requires `@_spi(Experimental) import Supabase`. + +```swift +do { + let passkey = try await supabase.auth.registerPasskey( + presentationAnchor: view.window! + ) + print("Registered passkey \(passkey.id)") +} catch { + // AuthError from the server, or user cancelled the native UI. + print(error) +} +``` + +For lower-level control (or on tvOS/watchOS), use `getPasskeyRegistrationOptions()` + `verifyPasskeyRegistration(challengeId:credentialResponse:)` from the [Auth Passkey](/docs/reference/swift/auth-passkey-api) reference. + @@ -195,7 +227,7 @@ The returned passkey contains the new credential's metadata: A friendly name is automatically derived from the authenticator's Authenticator Attestation GUID (AAGUID). For example, `iCloud Keychain`, `Google Password Manager`, `1Password`. Users can rename their passkey afterwards — see [Manage passkeys](#manage-passkeys). -See the `registerPasskey` reference ([JavaScript](/docs/reference/javascript/auth-registerpasskey) · [Dart](/docs/reference/dart/auth-registerpasskey)) for the full API. +See the `registerPasskey` reference ([JavaScript](/docs/reference/javascript/auth-registerpasskey) · [Dart](/docs/reference/dart/auth-registerpasskey) · [Swift](/docs/reference/swift/auth-registerpasskey)) for the full API. ## Sign in with a passkey @@ -234,10 +266,29 @@ try { } ``` + + + +Available on iOS 16+, macOS 13+, and visionOS 1+. Requires `@_spi(Experimental) import Supabase`. + +```swift +do { + let response = try await supabase.auth.signInWithPasskey( + presentationAnchor: view.window! + ) + // response.session and response.user are set; the client also fires a signedIn event. + print("Signed in as \(response.user?.email ?? "")") +} catch { + print(error) +} +``` + +For lower-level control (or on tvOS/watchOS), use `getPasskeyAuthenticationOptions()` + `verifyPasskeyAuthentication(challengeId:credentialResponse:)` from the [Auth Passkey](/docs/reference/swift/auth-passkey-api) reference. + -See the `signInWithPasskey` reference ([JavaScript](/docs/reference/javascript/auth-signinwithpasskey) · [Dart](/docs/reference/dart/auth-signinwithpasskey)) for the full API. +See the `signInWithPasskey` reference ([JavaScript](/docs/reference/javascript/auth-signinwithpasskey) · [Dart](/docs/reference/dart/auth-signinwithpasskey) · [Swift](/docs/reference/swift/auth-signinwithpasskey)) for the full API. ## Two-step API @@ -307,12 +358,41 @@ final AuthResponse res = await supabase.auth.passkey.verifyAuthentication( ); ``` + + + +Requires `@_spi(Experimental) import Supabase`. Works on all Apple platforms (iOS, macOS, tvOS, watchOS, visionOS). + +Registration: + +```swift +let options = try await supabase.auth.getPasskeyRegistrationOptions() +// Run the platform authenticator yourself (e.g. via ASAuthorizationController). +let credential: AnyJSON = try await runRegistrationCeremony(options.options) +let passkey = try await supabase.auth.verifyPasskeyRegistration( + challengeId: options.challengeId, + credentialResponse: credential +) +``` + +Authentication: + +```swift +let options = try await supabase.auth.getPasskeyAuthenticationOptions() +// Run the platform authenticator yourself (e.g. via ASAuthorizationController). +let credential: AnyJSON = try await runAuthenticationCeremony(options.options) +let response = try await supabase.auth.verifyPasskeyAuthentication( + challengeId: options.challengeId, + credentialResponse: credential +) +``` + -The `options` field returned from `startRegistration` and `startAuthentication` matches the [WebAuthn `PublicKeyCredentialCreationOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialcreationoptions) and [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptions) shapes (with `ArrayBuffer` fields encoded as base64url). +The `options` field returned from the start methods matches the [WebAuthn `PublicKeyCredentialCreationOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialcreationoptions) and [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrequestoptions) shapes (with `ArrayBuffer` fields encoded as base64url). -See the `auth.passkey` reference ([JavaScript](/docs/reference/javascript/auth-passkey-api) · [Dart](/docs/reference/dart/auth-passkey-api)) for the full API. +See the `auth.passkey` reference ([JavaScript](/docs/reference/javascript/auth-passkey-api) · [Dart](/docs/reference/dart/auth-passkey-api) · [Swift](/docs/reference/swift/auth-passkey-api)) for the full API. ## Manage passkeys @@ -359,12 +439,31 @@ await supabase.auth.passkey.update( await supabase.auth.passkey.delete(passkeyId: passkeys.first.id); ``` + + + +Requires `@_spi(Experimental) import Supabase`. + +```swift +// List +let passkeys: [PasskeyListItem] = try await supabase.auth.listPasskeys() + +// Rename +let updated = try await supabase.auth.renamePasskey( + id: passkeys.first!.id, + friendlyName: "Work laptop" +) + +// Delete +try await supabase.auth.deletePasskey(id: passkeys.first!.id) +``` + -`friendlyName` is limited to 120 characters. `last_used_at` is updated each time the passkey is used to sign in. +`friendlyName` is limited to 120 characters. `lastUsedAt` is updated each time the passkey is used to sign in. -See the `auth.passkey` reference ([JavaScript](/docs/reference/javascript/auth-passkey-api) · [Dart](/docs/reference/dart/auth-passkey-api)) for the full API. +See the `auth.passkey` reference ([JavaScript](/docs/reference/javascript/auth-passkey-api) · [Dart](/docs/reference/dart/auth-passkey-api) · [Swift](/docs/reference/swift/auth-passkey-api)) for the full API. ## Admin API @@ -410,7 +509,7 @@ await supabase.auth.admin.passkey.deletePasskey( -See the `auth.admin.passkey` reference ([JavaScript](/docs/reference/javascript/auth-admin-passkey-api) · [Dart](/docs/reference/dart/auth-admin-passkey-api)) for the full API. +See the `auth.admin.passkey` reference ([JavaScript](/docs/reference/javascript/auth-admin-passkey-api) · [Dart](/docs/reference/dart/auth-admin-passkey-api)) for the full API. The Swift SDK does not expose admin passkey methods. ## Error codes diff --git a/apps/docs/spec/supabase_swift_v2.yml b/apps/docs/spec/supabase_swift_v2.yml index adb19e7bc40..4fcb84eb1d3 100644 --- a/apps/docs/spec/supabase_swift_v2.yml +++ b/apps/docs/spec/supabase_swift_v2.yml @@ -679,6 +679,50 @@ functions: // Open the URL using your preferred method to complete sign-in process. UIApplication.shared.open(url) ``` + - id: sign-in-with-passkey + title: 'signInWithPasskey()' + notes: | + Signs the user in with a passkey (WebAuthn). Available on iOS 16+, macOS 13+, and visionOS 1+. + + - Drives the full WebAuthn ceremony end to end: fetches assertion options from the server, presents the native passkey UI via `AuthenticationServices`, and verifies the assertion. + - Does not require an existing session. On success the session is persisted and a `signedIn` auth change event is emitted. + - For lower-level control (custom authenticator, tvOS, watchOS), use `getPasskeyAuthenticationOptions()` + `verifyPasskeyAuthentication()` instead. + - Passkey support is **experimental**. Opt in with `@_spi(Experimental) import Supabase`. The API may change in future releases. + - Passkeys must be enabled for your project in the Dashboard under Authentication → Passkeys. + examples: + - id: sign-in-with-passkey + name: Sign in with a passkey + isSpotlight: true + code: | + ```swift + // iOS 16+/macOS 13+ only. Must opt in: @_spi(Experimental) import Supabase + let response = try await supabase.auth.signInWithPasskey( + presentationAnchor: view.window! + ) + let session = response.session + let user = response.user + ``` + - id: register-passkey + title: 'registerPasskey()' + notes: | + Registers a new passkey (WebAuthn credential) for the signed-in user. Available on iOS 16+, macOS 13+, and visionOS 1+. + + - Drives the full WebAuthn ceremony end to end: fetches creation options from the server, presents the native passkey registration UI, and stores the credential. + - Requires an authenticated, non-anonymous user. + - For lower-level control, use `getPasskeyRegistrationOptions()` + `verifyPasskeyRegistration()` instead. + - Passkey support is **experimental**. Opt in with `@_spi(Experimental) import Supabase`. + examples: + - id: register-passkey + name: Register a passkey for the current user + isSpotlight: true + code: | + ```swift + // iOS 16+/macOS 13+ only. Must opt in: @_spi(Experimental) import Supabase + let passkey = try await supabase.auth.registerPasskey( + presentationAnchor: view.window! + ) + print("Registered passkey \(passkey.id)") + ``` - id: sign-out title: 'signOut()' notes: | @@ -1217,16 +1261,19 @@ functions: notes: | This section contains methods commonly used for Multi-Factor Authentication (MFA) and are invoked behind the `supabase.auth.mfa` namespace. - Currently, we only support time-based one-time password (TOTP) as the 2nd factor. We don't support recovery codes but we allow users to enroll more than 1 TOTP factor, with an upper limit of 10. + TOTP (time-based one-time password) is the stable 2nd factor. WebAuthn / passkey as a 2nd factor is **experimental** — opt in with `@_spi(Experimental) import Supabase`. The first-factor passkey API lives in the `auth` (not `auth.mfa`) namespace; see the [Auth Passkey](/docs/reference/swift/auth-passkey-api) section. + + We don't support recovery codes but we allow users to enroll more than 1 TOTP factor, with an upper limit of 10. Having a 2nd TOTP factor for recovery frees the user of the burden of having to store their recovery codes somewhere. It also reduces the attack surface since multiple recovery codes are usually generated compared to just having 1 backup TOTP factor. - id: mfa-enroll title: 'mfa.enroll()' notes: | - - Currently, `totp` is the only supported `factorType`. The returned `id` should be used to create a challenge. + - Supported factor types: `totp` (stable) and `webauthn` (**experimental** — opt in with `@_spi(Experimental) import Supabase`). The returned `id` should be used to create a challenge. - To create a challenge, see [`mfa.challenge()`](/docs/reference/swift/auth-mfa-challenge). - To verify a challenge, see [`mfa.verify()`](/docs/reference/swift/auth-mfa-verify). - To create and verify a challenge in a single step, see [`mfa.challengeAndVerify()`](/docs/reference/swift/auth-mfa-challengeandverify). + - For a one-call WebAuthn enrollment on iOS 16+/macOS 13+, use `mfa.enrollWebAuthnFactor(friendlyName:presentationAnchor:)` instead. examples: - id: enroll-totp-factor @@ -1250,6 +1297,17 @@ functions: let secret = response.totp?.secret let uri = response.totp?.uri ``` + - id: enroll-webauthn-factor + name: Enroll a WebAuthn (passkey) factor (iOS 16+/macOS 13+, experimental) + code: | + ```swift + // @_spi(Experimental) import Supabase + // enrollWebAuthnFactor drives the full ceremony: enroll → challenge → present native UI → verify. + let verifyResponse = try await supabase.auth.mfa.enrollWebAuthnFactor( + friendlyName: "My passkey", + presentationAnchor: view.window! + ) + ``` - id: mfa-challenge title: 'mfa.challenge()' notes: | @@ -1271,6 +1329,7 @@ functions: title: 'mfa.verify()' notes: | - To verify a challenge, please [create a challenge](/docs/reference/swift/auth-mfa-challenge) first. + - For WebAuthn factors on iOS 16+/macOS 13+, `mfa.verifyWebAuthnFactor(factorId:presentationAnchor:)` drives the full challenge + native UI + verify flow in one call (**experimental** — opt in with `@_spi(Experimental) import Supabase`). examples: - id: verify-challenge name: Verify a challenge for a factor @@ -1285,6 +1344,17 @@ functions: ) ) ``` + - id: verify-webauthn-factor + name: Verify a WebAuthn factor (iOS 16+/macOS 13+, experimental) + code: | + ```swift + // @_spi(Experimental) import Supabase + // verifyWebAuthnFactor drives the full ceremony: challenge → present native UI → verify. + let session = try await supabase.auth.mfa.verifyWebAuthnFactor( + factorId: "34e770dd-9ff9-416c-87fa-43b31d7ef225", + presentationAnchor: view.window! + ) + ``` - id: mfa-challenge-and-verify title: 'mfa.challengeAndVerify()' notes: | @@ -1347,6 +1417,157 @@ functions: ```swift let factors = try await supabase.auth.mfa.listFactors() ``` + - id: passkey-api + title: 'Overview' + notes: | + Lower-level passkey methods for custom authenticator flows and platforms where `AuthenticationServices` is unavailable (tvOS, watchOS). + + These methods handle the network side of the WebAuthn ceremony only — the caller is responsible for driving the platform authenticator between fetching options and submitting the credential response. For an end-to-end flow on iOS 16+/macOS 13+, prefer `signInWithPasskey(presentationAnchor:)` and `registerPasskey(presentationAnchor:)`. + + Passkey support is **experimental**. Opt in with `@_spi(Experimental) import Supabase`. The API may change in future releases. + - id: passkey-list + title: 'listPasskeys()' + notes: | + Returns the list of passkeys registered to the signed-in user. + examples: + - id: list-passkeys + name: List the current user's passkeys + isSpotlight: true + code: | + ```swift + // @_spi(Experimental) import Supabase + let passkeys: [PasskeyListItem] = try await supabase.auth.listPasskeys() + ``` + - id: passkey-update + title: 'renamePasskey(id:friendlyName:)' + notes: | + Updates the friendly name of a passkey. Limited to 120 characters. + params: + - name: id + isOptional: false + type: String + description: ID of the passkey to rename. + - name: friendlyName + isOptional: false + type: String + description: New human readable name. Limited to 120 characters. + examples: + - id: rename-passkey + name: Rename a passkey + isSpotlight: true + code: | + ```swift + // @_spi(Experimental) import Supabase + let passkey = try await supabase.auth.renamePasskey( + id: "34e770dd-9ff9-416c-87fa-43b31d7ef225", + friendlyName: "Work laptop" + ) + ``` + - id: passkey-delete + title: 'deletePasskey(id:)' + notes: | + Removes a passkey from the signed-in user's account. + params: + - name: id + isOptional: false + type: String + description: ID of the passkey to delete. + examples: + - id: delete-passkey + name: Delete a passkey + isSpotlight: true + code: | + ```swift + // @_spi(Experimental) import Supabase + try await supabase.auth.deletePasskey(id: "34e770dd-9ff9-416c-87fa-43b31d7ef225") + ``` + - id: passkey-start-registration + title: 'getPasskeyRegistrationOptions()' + notes: | + Fetches credential creation options to register a new passkey for the signed-in user. + + - Pass the returned `options` (W3C `PublicKeyCredentialCreationOptions`) to the platform authenticator. + - After running the authenticator, submit the result with `verifyPasskeyRegistration(challengeId:credentialResponse:)`. + examples: + - id: start-passkey-registration + name: Get passkey registration options + isSpotlight: true + code: | + ```swift + // @_spi(Experimental) import Supabase + let options: PasskeyRegistrationOptions = try await supabase.auth.getPasskeyRegistrationOptions() + // Hand options.options to the platform authenticator. + ``` + - id: passkey-verify-registration + title: 'verifyPasskeyRegistration(challengeId:credentialResponse:)' + notes: | + Stores a newly created passkey for the signed-in user. + params: + - name: challengeId + isOptional: false + type: String + description: The `challengeId` returned by `getPasskeyRegistrationOptions()`. + - name: credentialResponse + isOptional: false + type: AnyJSON + description: The W3C `RegistrationResponseJSON` credential produced by the authenticator. + examples: + - id: verify-passkey-registration + name: Verify a passkey registration + isSpotlight: true + code: | + ```swift + // @_spi(Experimental) import Supabase + let passkey: PasskeyListItem = try await supabase.auth.verifyPasskeyRegistration( + challengeId: options.challengeId, + credentialResponse: credential + ) + ``` + - id: passkey-start-authentication + title: 'getPasskeyAuthenticationOptions()' + notes: | + Fetches assertion options to authenticate with a passkey. + + - Does not require an existing session. + - Pass the returned `options` (W3C `PublicKeyCredentialRequestOptions`) to the platform authenticator. + - After running the authenticator, submit the result with `verifyPasskeyAuthentication(challengeId:credentialResponse:)`. + examples: + - id: start-passkey-authentication + name: Get passkey authentication options + isSpotlight: true + code: | + ```swift + // @_spi(Experimental) import Supabase + let options: PasskeyAuthenticationOptions = try await supabase.auth.getPasskeyAuthenticationOptions() + // Hand options.options to the platform authenticator. + ``` + - id: passkey-verify-authentication + title: 'verifyPasskeyAuthentication(challengeId:credentialResponse:)' + notes: | + Verifies a passkey assertion and establishes a session. On success the session is persisted and a `signedIn` auth change event is emitted. + params: + - name: challengeId + isOptional: false + type: String + description: The `challengeId` returned by `getPasskeyAuthenticationOptions()`. + - name: credentialResponse + isOptional: false + type: AnyJSON + description: The W3C `AuthenticationResponseJSON` assertion produced by the authenticator. + examples: + - id: verify-passkey-authentication + name: Verify a passkey sign in + isSpotlight: true + code: | + ```swift + // @_spi(Experimental) import Supabase + let response: AuthResponse = try await supabase.auth.verifyPasskeyAuthentication( + challengeId: options.challengeId, + credentialResponse: credential + ) + let session = response.session + let user = response.user + ``` - id: admin-api title: 'Overview' notes: | diff --git a/supa-mdx-lint/Rule003Spelling.toml b/supa-mdx-lint/Rule003Spelling.toml index 184d11e9e70..6815878a6e4 100644 --- a/supa-mdx-lint/Rule003Spelling.toml +++ b/supa-mdx-lint/Rule003Spelling.toml @@ -495,6 +495,7 @@ allow_list = [ "tokio", "tsvector", "tvOS", + "visionOS", "uBlock Origin", "unbilled", "unpublish",