mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 13:19:09 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## Problem The Docs E2E link checker found broken links throughout docs, starting with `phone-login.mdx` pointing to `/docs/guides/cli/config` (404). Old links like `/docs/guides/cli/config` still work on the live site because `supabase.com` has redirects set up for them, but these links break on the docs preview site, which is what the E2E check tests against. These issues look clean on the live site, and I didn't catch them in my first pass because I was testing production instead of the preview. The E2E check only tests the ~20 pages a given PR happens to touch, so fixing the pages it flagged kept exposing more of the same problem one page at a time as each fix pulled in a new file. To stop chasing this incrementally, I cross-referenced every `/docs/guides/*` and `/docs/reference/*` redirect source in `apps/www/lib/redirects.js` against actual usage across all of `apps/docs`, and verified each candidate against the live preview. ## Solution Rather than updating the Docs E2E link checker, this PR resolves the links. **Why:** we own these docs, so keeping the links clean without redirects is keeping the house maintained. See [Broken Window Theory](https://blog.codinghorror.com/the-broken-window-theory/). Updated every link still using an old path to point straight at the current page instead of relying on a redirect. This covers old links like: - `/docs/guides/cli/config` → `/docs/guides/local-development/cli/config` - `/docs/guides/cli/getting-started` → `/docs/guides/local-development/cli/getting-started` - `/docs/guides/cli/local-development` → `/docs/guides/local-development/database-migrations` - `/docs/guides/cli/managing-environments` → `/docs/guides/deployment/managing-environments` - `/docs/guides/cli/seeding-your-database` → `/docs/guides/local-development/seeding-your-database` - bare `/docs/guides/cli` → `/docs/guides/local-development` - `/docs/guides/platform/compute-add-ons` → `/docs/guides/platform/compute-and-disk` - `/docs/guides/platform/shared-responsibility-model` → `/docs/guides/deployment/shared-responsibility-model` - `/docs/guides/database` → `/docs/guides/database/overview` - `/docs/reference/javascript`, `/docs/reference/dart`, `/docs/reference/kotlin`, `/docs/reference/python`, `/docs/reference/csharp` → their `/introduction` pages (the redirect's own destination, `/start`, turned out to be dead even on production — a separate bug in `redirects.js` I didn't touch here) - and about 35 more of the same pattern, listed in the commit messages Also fixed a handful of dead heading anchors found along the way (links that resolve to the right page but point at a `#section` that got renamed or moved), including the original `#bigquery` anchor and a few in `connecting-to-postgres.mdx` where content moved to its own dedicated page. Left alone on purpose: - `content/guides/cli.mdx` — this page has no route in the docs app at all (no `app/guides/cli/` directory), so it 404s even in production before the `www` redirect ever fires. Fixing its internal link wouldn't change that; it needs an actual routing/content decision, not a link fix. - A few candidates that already resolve fine as-is (`pg_partman`, bare `/docs/reference/api`, bare `/docs/reference/cli`) — confirmed via curl, left untouched. ## Manual testing 1. Confirmed every new link target actually exists by checking the destination file/page and matching heading anchors. 2. Cross-referenced every `/docs/guides/*` and `/docs/reference/*` redirect source in `apps/www/lib/redirects.js` against real usage in `apps/docs`, and curl-verified each old path (404) and new path (200) against the live PR preview before fixing it. 3. Ran the Docs E2E link checker locally against changed pages. 4. Spot-checked the original broken link from CI (`/docs/guides/cli/config`) to confirm it now points to a working page.
383 lines
13 KiB
Plaintext
383 lines
13 KiB
Plaintext
---
|
|
id: 'auth-anonymous'
|
|
title: 'Anonymous Sign-Ins'
|
|
subtitle: 'Create and use anonymous users to authenticate with Supabase'
|
|
---
|
|
|
|
[Enable Anonymous Sign-Ins](/dashboard/project/_/auth/providers) to build apps which provide users an authenticated experience without requiring users to enter an email address, password, use an OAuth provider or provide any other PII (Personally Identifiable Information). Later, when ready, the user can link an authentication method to their account.
|
|
|
|
<Admonition type="note" title="Anonymous user vs the anon key">
|
|
|
|
Calling `signInAnonymously()` creates an anonymous user. It behaves like a permanent user, except the user can't access their account if they sign out, clear browsing data, or use another device.
|
|
|
|
Like permanent users, the `authenticated` Postgres role will be used when using the Data APIs to access your project. JWTs for these users will have an `is_anonymous` claim which you can use to distinguish in RLS policies.
|
|
|
|
This is different from the `anon` API key which does not create a user and can be used to implement public access to your database as it uses the `anonymous` Postgres role.
|
|
|
|
</Admonition>
|
|
|
|
Anonymous sign-ins can be used to build:
|
|
|
|
- E-commerce applications, such as shopping carts before check-out
|
|
- Full-feature demos without collecting personal information
|
|
- Temporary or throw-away accounts
|
|
|
|
<Admonition type="caution">
|
|
|
|
Review your existing RLS policies before enabling anonymous sign-ins. Anonymous users use the `authenticated` role. To distinguish between anonymous users and permanent users, your policies need to check the `is_anonymous` field of the user's JWT.
|
|
|
|
See the [Access control section](#access-control) for more details.
|
|
|
|
</Admonition>
|
|
|
|
<Admonition type="caution" title="Use Dynamic Rendering with Next.js">
|
|
|
|
The Supabase team has received reports of user metadata being cached across unique anonymous users as a result of Next.js static page rendering. For the best user experience, use dynamic page rendering.
|
|
|
|
</Admonition>
|
|
|
|
<Admonition type="note" title="Self hosting and local development">
|
|
|
|
For self-hosting, you can update your project configuration using the files and environment variables provided. See the [local development docs](/docs/guides/local-development/cli/config) for more details.
|
|
|
|
</Admonition>
|
|
|
|
## Sign in anonymously
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
Call the [`signInAnonymously()`](/docs/reference/javascript/auth-signinanonymously) method:
|
|
|
|
```js
|
|
import { createClient } from '@supabase/supabase-js'
|
|
|
|
const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')
|
|
|
|
// ---cut---
|
|
const { data, error } = await supabase.auth.signInAnonymously()
|
|
```
|
|
|
|
</TabPanel>
|
|
<$Show if="sdk:dart">
|
|
<TabPanel id="dart" label="Flutter">
|
|
|
|
Call the [`signInAnonymously()`](/docs/reference/dart/auth-signinanonymously) method:
|
|
|
|
```dart
|
|
await supabase.auth.signInAnonymously();
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:swift">
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
Call the [`signInAnonymously()`](/docs/reference/swift/auth-signinanonymously) method:
|
|
|
|
```swift
|
|
let session = try await supabase.auth.signInAnonymously()
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:kotlin">
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
Call the [`signInAnonymously()`](/docs/reference/kotlin/auth-signinanonymously) method:
|
|
|
|
```kotlin
|
|
supabase.auth.signInAnonymously()
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:python">
|
|
<TabPanel id="python" label="Python">
|
|
|
|
Call the [`sign_in_anonymously()`](/docs/reference/python/auth-signinanonymously) method:
|
|
|
|
```python
|
|
response = supabase.auth.sign_in_anonymously()
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
</Tabs>
|
|
|
|
## Convert an anonymous user to a permanent user
|
|
|
|
Converting an anonymous user to a permanent user requires [linking an identity](/docs/guides/auth/auth-identity-linking#manual-linking-beta) to the user. This requires you to [enable manual linking](/dashboard/project/_/auth/providers) in your Supabase project.
|
|
|
|
### Link an email / phone identity
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
You can use the [`updateUser()`](/docs/reference/javascript/auth-updateuser) method to link an email or phone identity to the anonymous user. To add a password for the anonymous user, the user's email or phone number needs to be verified first.
|
|
|
|
```js
|
|
import { createClient } from '@supabase/supabase-js'
|
|
|
|
const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')
|
|
|
|
// ---cut---
|
|
const { data: updateEmailData, error: updateEmailError } = await supabase.auth.updateUser({
|
|
email: 'valid.email@supabase.io',
|
|
})
|
|
|
|
// verify the user's email by clicking on the email change link
|
|
// or entering the 6-digit OTP sent to the email address
|
|
|
|
// once the user has been verified, update the password
|
|
const { data: updatePasswordData, error: updatePasswordError } = await supabase.auth.updateUser({
|
|
password: 'password',
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<$Show if="sdk:dart">
|
|
<TabPanel id="dart" label="Flutter">
|
|
|
|
You can use the [`updateUser()`](/docs/reference/dart/auth-updateuser) method to link an email or phone identity to the anonymous user.
|
|
|
|
```dart
|
|
await supabase.auth.updateUser(UserAttributes(email: 'valid.email@supabase.io'));
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:swift">
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
You can use the [`update(user:)`](/docs/reference/swift/auth-updateuser) method to link an email or phone identity to the anonymous user.
|
|
|
|
```swift
|
|
try await supabase.auth.update(
|
|
user: UserAttributes(email: "valid.email@supabase.io")
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:kotlin">
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
You can use the [`updateUser()`](/docs/reference/kotlin/auth-updateuser) method to link an email or phone identity to the anonymous user.
|
|
|
|
```kotlin
|
|
supabase.auth.updateUser {
|
|
email = "valid.email@supabase.io"
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:python">
|
|
<TabPanel id="python" label="Python">
|
|
|
|
You can use the [`update_user()`](/docs/reference/python/auth-updateuser) method to link an email or phone identity to the anonymous user. To add a password for the anonymous user, the user's email or phone number needs to be verified first.
|
|
|
|
```python
|
|
response = supabase.auth.update_user({
|
|
'email': 'valid.email@supabase.io',
|
|
})
|
|
|
|
# verify the user's email by clicking on the email change link
|
|
# or entering the 6-digit OTP sent to the email address
|
|
|
|
# once the user has been verified, update the password
|
|
response = supabase.auth.update_user({
|
|
'password': 'password',
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
</Tabs>
|
|
|
|
### Link an OAuth identity
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
You can use the [`linkIdentity()`](/docs/reference/javascript/auth-linkidentity) method to link an OAuth identity to the anonymous user.
|
|
|
|
```js
|
|
import { createClient } from '@supabase/supabase-js'
|
|
|
|
const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')
|
|
|
|
// ---cut---
|
|
const { data, error } = await supabase.auth.linkIdentity({ provider: 'google' })
|
|
```
|
|
|
|
</TabPanel>
|
|
<$Show if="sdk:dart">
|
|
<TabPanel id="dart" label="Flutter">
|
|
|
|
You can use the [`linkIdentity()`](/docs/reference/dart/auth-linkidentity) method to link an OAuth identity to the anonymous user.
|
|
|
|
```dart
|
|
await supabase.auth.linkIdentity(OAuthProvider.google);
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:swift">
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
You can use the [`linkIdentity()`](/docs/reference/swift/auth-linkidentity) method to link an OAuth identity to the anonymous user.
|
|
|
|
```swift
|
|
try await supabase.auth.linkIdentity(provider: .google)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:kotlin">
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
You can use the [`linkIdentity()`](/docs/reference/kotlin/auth-linkidentity) method to link an OAuth identity to the anonymous user.
|
|
|
|
```kotlin
|
|
supabase.auth.linkIdentity(Google)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:python">
|
|
<TabPanel id="python" label="Python">
|
|
|
|
You can use the [`link_identity()`](/docs/reference/python/auth-linkidentity) method to link an OAuth identity to the anonymous user.
|
|
|
|
```python
|
|
response = supabase.auth.link_identity({'provider': 'google'})
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
</Tabs>
|
|
|
|
## Access control
|
|
|
|
An anonymous user assumes the `authenticated` role like a permanent user. You can use row-level security (RLS) policies to differentiate between an anonymous user and a permanent user by checking for the `is_anonymous` claim in the JWT returned by `auth.jwt()`:
|
|
|
|
```sql
|
|
create policy "Only permanent users can post to the news feed"
|
|
on news_feed as restrictive for insert
|
|
to authenticated
|
|
with check ((select (auth.jwt()->>'is_anonymous')::boolean) is false );
|
|
|
|
create policy "Anonymous and permanent users can view the news feed"
|
|
on news_feed for select
|
|
to authenticated
|
|
using ( true );
|
|
```
|
|
|
|
<Admonition type="note" title="Use restrictive policies">
|
|
|
|
RLS policies are permissive by default, which means that they are combined using an "OR" operator when multiple policies are applied. It is important to construct restrictive policies to ensure that the checks for an anonymous user are always enforced when combined with other policies.
|
|
Be aware that a single 'restrictive' RLS policy alone will fail unless combined with another policy that returns true, ensuring the combined condition is met.
|
|
|
|
</Admonition>
|
|
|
|
## Resolving identity conflicts
|
|
|
|
Depending on your application requirements, data conflicts can arise when an anonymous user is converted to a permanent user. For example, in the context of an e-commerce application, an anonymous user would be allowed to add items to the shopping cart without signing up / signing in. When they decide to sign-in to an existing account, you will need to decide how you want to resolve data conflicts in the shopping cart:
|
|
|
|
1. Overwrite the items in the cart with those in the existing account
|
|
2. Overwrite the items in the cart with those from the anonymous user
|
|
3. Merge the items in the cart together
|
|
|
|
### Linking an anonymous user to an existing account
|
|
|
|
In some cases, you may need to link an anonymous user to an existing account rather than creating a new permanent account. This process requires manual handling of potential conflicts. Here's a general approach:
|
|
|
|
```javascript
|
|
// 1. Get the current session and verify the user is anonymous
|
|
const { data: anonData, error: anonError } = await supabase.auth.getSession()
|
|
|
|
if (!anonData.session?.user?.is_anonymous) {
|
|
console.log('User is not anonymous. This flow only applies to anonymous users.')
|
|
return
|
|
}
|
|
|
|
// 2. Attempt to update the user with the existing email
|
|
const { data: updateData, error: updateError } = await supabase.auth.updateUser({
|
|
email: 'valid.email@supabase.io',
|
|
})
|
|
|
|
// 3. Handle the error (since the email belongs to an existing user)
|
|
if (updateError) {
|
|
console.log('This email belongs to an existing user. Please sign in to that account.')
|
|
|
|
// 4. Sign in to the existing account
|
|
const {
|
|
data: { user: existingUser },
|
|
error: signInError,
|
|
} = await supabase.auth.signInWithPassword({
|
|
email: 'valid.email@supabase.io',
|
|
password: 'user_password',
|
|
})
|
|
|
|
if (existingUser) {
|
|
// 5. Reassign entities tied to the anonymous user
|
|
// This step will vary based on your specific use case and data model
|
|
const { data: reassignData, error: reassignError } = await supabase
|
|
.from('your_table')
|
|
.update({ user_id: existingUser.id })
|
|
.eq('user_id', anonData.session.user.id)
|
|
|
|
// 6. Implement your chosen conflict resolution strategy
|
|
// This could involve merging data, overwriting, or other custom logic
|
|
await resolveDataConflicts(anonData.session.user.id, existingUser.id)
|
|
}
|
|
}
|
|
|
|
// Helper function to resolve data conflicts (implement based on your strategy)
|
|
async function resolveDataConflicts(anonymousUserId, existingUserId) {
|
|
// Implement your conflict resolution logic here
|
|
// This could involve ignoring the anonymous user's metadata, overwriting the existing user's metadata, or merging the data of both the anonymous and existing user.
|
|
}
|
|
```
|
|
|
|
## Abuse prevention and rate limits
|
|
|
|
Since anonymous users are stored in your database, bad actors can abuse the endpoint to increase your database size drastically. It is strongly recommended to [enable invisible CAPTCHA or Cloudflare Turnstile](/docs/guides/auth/auth-captcha) to prevent abuse for anonymous sign-ins. An IP-based rate limit is enforced at 30 requests per hour which can be modified in your [dashboard](/dashboard/project/_/auth/rate-limits). You can refer to the full list of rate limits [here](/docs/guides/deployment/going-into-prod#rate-limiting-resource-allocation--abuse-prevention).
|
|
|
|
## Automatic cleanup
|
|
|
|
Automatic cleanup of anonymous users is currently not available. Instead, you can delete anonymous users from your project by running the following SQL:
|
|
|
|
```sql
|
|
-- deletes anonymous users created more than 30 days ago
|
|
delete from auth.users
|
|
where is_anonymous is true and created_at < now() - interval '30 days';
|
|
```
|
|
|
|
## Resources
|
|
|
|
- [Supabase - Get started for free](https://supabase.com)
|
|
- [Supabase JS Client](https://github.com/supabase/supabase-js)
|
|
- [Supabase Flutter Client](https://github.com/supabase/supabase-flutter)
|
|
- [Supabase Kotlin Client](https://github.com/supabase-community/supabase-kt)
|