Skip to content
Merged
57 changes: 57 additions & 0 deletions apps/docs/content/guides/auth/auth-identity-linking.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,63 @@ response = supabase.auth.link_identity({'provider': 'google'})

In the example above, the user will be redirected to Google to complete the OAuth2.0 flow. Once the OAuth2.0 flow has completed successfully, the user will be redirected back to the application and the Google identity will be linked to the user. You can enable manual linking from your project's authentication [configuration options](/dashboard/project/_/auth/providers) or by setting the environment variable `GOTRUE_SECURITY_MANUAL_LINKING_ENABLED: true` when self-hosting.

### Link identity with native OAuth (ID token)

<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">

For native mobile applications, you can link an identity using an ID token obtained from a third-party OAuth provider. This is useful when you want to use native OAuth flows (like Google Sign-In or Sign in with Apple) rather than web-based OAuth redirects.

```js
// Example with Google Sign-In (using a native Google Sign-In library)
const idToken = 'ID_TOKEN_FROM_GOOGLE'
const accessToken = 'ACCESS_TOKEN_FROM_GOOGLE'

const { data, error } = await supabase.auth.linkIdentityWithIdToken({
provider: 'google',
token: idToken,
access_token: accessToken,
})
```

</TabPanel>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">

For Flutter applications, you can link an identity using an ID token obtained from native OAuth packages like `google_sign_in` or `sign_in_with_apple`. Call [`linkIdentityWithIdToken()`](/docs/reference/dart/auth-linkidentitywithidtoken):

```dart
import 'package:google_sign_in/google_sign_in.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

// First, obtain the ID token from the native provider
final GoogleSignIn googleSignIn = GoogleSignIn(
clientId: iosClientId,
serverClientId: webClientId,
);
final googleUser = await googleSignIn.signIn();
final googleAuth = await googleUser!.authentication;

// Link the Google identity to the current user
final response = await supabase.auth.linkIdentityWithIdToken(
provider: OAuthProvider.google,
idToken: googleAuth.idToken!,
accessToken: googleAuth.accessToken!,
);
```

This method supports the same OAuth providers as `signInWithIdToken()`: Google, Apple, Facebook, Kakao, and Keycloak.

</TabPanel>
</$Show>
</Tabs>

## Unlink an identity

<Tabs
Expand Down
1 change: 1 addition & 0 deletions apps/docs/content/guides/getting-started/features.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ In addition to the Beta requirements, features in GA are covered by the [uptime
| Platform | Terraform Provider | `public alpha` | N/A |
| Platform | Read Replicas | `GA` | N/A |
| Platform | Log Drains | `public alpha` | ✅ |
| Platform | MCP | `public alpha` | ✅ |
| Studio | | `GA` | ✅ |
| Studio | SSO | `GA` | ✅ |
| Studio | Column Privileges | `public alpha` | ✅ |
Expand Down
154 changes: 154 additions & 0 deletions apps/docs/spec/supabase_dart_v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1352,6 +1352,106 @@ functions:
// unlink the google identity
await supabase.auth.unlinkIdentity(googleIdentity);
```
- id: link-identity-with-id-token
title: 'linkIdentityWithIdToken()'
description: |
Links an identity to an existing user using an ID token obtained from a third-party OAuth provider. This allows linking identities using native OAuth flows (Google, Apple, Facebook, etc.) similar to `signInWithIdToken()` but for linking rather than signing in.
notes: |
- The **Enable Manual Linking** option must be enabled from your [project's authentication settings](/dashboard/project/_/settings/auth).
- The user needs to be signed in to call `linkIdentityWithIdToken()`.
- Supports the same OAuth providers as `signInWithIdToken()`: Google, Apple, Facebook, Kakao, and Keycloak.
- If the candidate identity is already linked to another user, the operation will fail.
params:
- name: provider
isOptional: false
type: OAuthProvider
description: The OAuth provider to link the identity from.
- name: idToken
isOptional: false
type: String
description: The identity token obtained from the third-party provider.
- name: accessToken
isOptional: true
type: String
description: Access token obtained from the third-party provider. Required for Google sign in.
- name: nonce
isOptional: true
type: String
description: Raw nonce value used to perform the third-party sign in. Required for Apple sign-in.
- name: captchaToken
isOptional: true
type: String
description: The captcha token to be used for captcha verification.
examples:
- id: link-google-identity
name: Link Google identity
isSpotlight: true
description: |
Link a Google identity to the currently signed-in user using native Google Sign-In.
code: |
```dart
import 'package:google_sign_in/google_sign_in.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

const webClientId = '<web client ID>';
const iosClientId = '<iOS client ID>';

final GoogleSignIn googleSignIn = GoogleSignIn(
clientId: iosClientId,
serverClientId: webClientId,
);
final googleUser = await googleSignIn.signIn();
final googleAuth = await googleUser!.authentication;
final accessToken = googleAuth.accessToken;
final idToken = googleAuth.idToken;

if (accessToken == null) {
throw 'No Access Token found.';
}
if (idToken == null) {
throw 'No ID Token found.';
}

final response = await supabase.auth.linkIdentityWithIdToken(
provider: OAuthProvider.google,
idToken: idToken,
accessToken: accessToken,
);
```
- id: link-apple-identity
name: Link Apple identity
description: |
Link an Apple identity to the currently signed-in user using native Apple Sign In.
code: |
```dart
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:crypto/crypto.dart';

final rawNonce = supabase.auth.generateRawNonce();
final hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();

final credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: hashedNonce,
);

final idToken = credential.identityToken;
if (idToken == null) {
throw const AuthException(
'Could not find ID Token from generated credential.',
);
}

final response = await supabase.auth.linkIdentityWithIdToken(
provider: OAuthProvider.apple,
idToken: idToken,
nonce: rawNonce,
);
```
- id: send-password-reauthentication
title: 'reauthenticate()'
notes: |
Expand Down Expand Up @@ -4917,6 +5017,60 @@ functions:
}
]
```
- id: max-affected
title: maxAffected()
description: |
Sets the maximum number of rows that can be affected by the query. Only effective with PATCH and DELETE operations. Requires PostgREST v13 or higher.

When the limit is exceeded, the query will fail with an error. This provides a safety mechanism to prevent accidentally affecting more rows than intended.
notes: |
- This method is only effective with UPDATE and DELETE operations.
- Requires PostgREST v13 or higher on your Supabase instance.
- If the number of affected rows exceeds the limit, the query will fail and no rows will be modified.
params:
- name: count
isOptional: false
type: int
description: The maximum number of rows that can be affected by the query.
examples:
- id: with-update
name: With update()
isSpotlight: true
code: |
```dart
await supabase
.from('users')
.update({'active': false})
.eq('status', 'inactive')
.maxAffected(5);
```
description: |
Limit the number of rows that can be updated. If more than 5 rows match the filter, the operation will fail.
- id: with-delete
name: With delete()
code: |
```dart
await supabase
.from('users')
.delete()
.eq('active', false)
.maxAffected(10);
```
description: |
Limit the number of rows that can be deleted. If more than 10 rows match the filter, the operation will fail.
- id: with-select
name: With select()
code: |
```dart
final data = await supabase
.from('users')
.update({'status': 'INACTIVE'})
.eq('id', 1)
.maxAffected(1)
.select();
```
description: |
Combine maxAffected with select() to limit affected rows and return the updated data.
- id: single
title: single()
description: |
Expand Down
93 changes: 93 additions & 0 deletions apps/docs/spec/supabase_py_v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6076,6 +6076,98 @@ functions:
}
```
hideCodeBlock: true
- id: max-affected
title: max_affected()
description: |
Set the maximum number of rows that can be affected by an update or delete operation.
If the query would affect more rows than the specified limit, the operation will fail with an error.

This uses PostgREST's `Prefer: max-affected` header with `handling=strict`.
notes: |
- Only available in PostgREST v13+
- Only works with PATCH (update) and DELETE methods
- The operation will fail if it would affect more rows than specified
params:
- name: value
isOptional: false
type: int
description: The maximum number of rows that can be affected
examples:
- id: with-update
name: Limit affected rows on update
code: |
```python
response = (
supabase.table("planets")
.update({"name": "Updated"})
.eq("id", 1)
.max_affected(1)
.execute()
)
```
data:
sql: |
```sql
create table
planets (id int8 primary key, name text);

insert into
planets (id, name)
values
(1, 'Mercury'),
(2, 'Earth');
```
response: |
```json
{
"data": [
{
"id": 1,
"name": "Updated"
}
],
"count": null
}
```
hideCodeBlock: true
isSpotlight: true
- id: with-delete
name: Limit affected rows on delete
code: |
```python
response = (
supabase.table("planets")
.delete()
.eq("id", 1)
.max_affected(1)
.execute()
)
```
data:
sql: |
```sql
create table
planets (id int8 primary key, name text);

insert into
planets (id, name)
values
(1, 'Mercury'),
(2, 'Earth');
```
response: |
```json
{
"data": [
{
"id": 1,
"name": "Mercury"
}
],
"count": null
}
```
hideCodeBlock: true
- id: using-modifiers
title: Using Modifiers
description: |
Expand Down Expand Up @@ -6832,6 +6924,7 @@ functions:
title: on().subscribe()
notes: |
- By default, Broadcast and Presence are enabled for all projects.
- Presence is automatically enabled when you attach presence callbacks (`on_presence_sync()`, `on_presence_join()`, or `on_presence_leave()`). If you add presence callbacks to an already joined channel, the channel will automatically resubscribe with presence enabled.
- By default, listening to database changes is disabled for new projects due to database performance and security concerns. You can turn it on by managing Realtime's [replication](/docs/guides/api#realtime-api-overview).
- You can receive the "previous" data for updates and deletes by setting the table's `REPLICA IDENTITY` to `FULL` (e.g., `ALTER TABLE your_table REPLICA IDENTITY FULL;`).
- Row level security is not applied to delete statements. When RLS is enabled and replica identity is set to full, only the primary key is sent to clients.
Expand Down
Loading
Loading