Auth

Anonymous Sign-Ins

Create and use anonymous users to authenticate with Supabase


Enable Anonymous Sign-Ins 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.

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

Sign in anonymously

Call the signInAnonymously() method:


_10
const { data, error } = await supabase.auth.signInAnonymously()

Convert an anonymous user to a permanent user

Converting an anonymous user to a permanent user requires linking an identity to the user. This requires you to enable manual linking in your Supabase project.

You can use the 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.


_10
const { data, error } = await supabase.auth.updateUser({ email: 'example@email.com' })
_10
_10
// verify the user's email by clicking on the email change link
_10
// or entering the 6-digit OTP sent to the email address
_10
_10
// once the user has been verified, update the password
_10
const { data, error } = await supabase.auth.updateUser({ password: 'password' })

You can use the linkIdentity() method to link an oauth identity to the anonymous user.


_10
const { data, error } = await supabase.auth.linkIdentity({ provider: 'google' })

Access control

An anonymous user assumes the authenticated role just 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():


_10
create policy "Only permanent users can post to the news feed"
_10
on news_feed as restrictive for insert
_10
to authenticated
_10
with check ((select (auth.jwt()->>'is_anonymous')::boolean) is false );
_10
_10
create policy "Anonymous and permanent users can view the news feed"
_10
on news_feed for select
_10
to authenticated
_10
using ( true );

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:


_40
// 1. Sign in anonymously (assuming the user is already signed in anonymously)
_40
const { data: anonData, error: anonError } = await supabase.auth.getSession()
_40
_40
// 2. Attempt to update the user with the existing email
_40
const { data: updateData, error: updateError } = await supabase.auth.updateUser({
_40
email: 'existing_user@example.com',
_40
})
_40
_40
// 3. Handle the error (since the email belongs to an existing user)
_40
if (updateError) {
_40
console.log('This email belongs to an existing user. Please sign in to that account.')
_40
_40
// 4. Sign in to the existing account
_40
const {
_40
data: { user: existingUser },
_40
error: signInError,
_40
} = await supabase.auth.signInWithPassword({
_40
email: 'existing_user@example.com',
_40
password: 'user_password',
_40
})
_40
_40
if (existingUser) {
_40
// 5. Reassign entities tied to the anonymous user
_40
// This step will vary based on your specific use case and data model
_40
const { data: reassignData, error: reassignError } = await supabase
_40
.from('your_table')
_40
.update({ user_id: existingUser.id })
_40
.eq('user_id', anonData.session.user.id)
_40
_40
// 6. Implement your chosen conflict resolution strategy
_40
// This could involve merging data, overwriting, or other custom logic
_40
await resolveDataConflicts(anonData.session.user.id, existingUser.id)
_40
}
_40
}
_40
_40
// Helper function to resolve data conflicts (implement based on your strategy)
_40
async function resolveDataConflicts(anonymousUserId, existingUserId) {
_40
// Implement your conflict resolution logic here
_40
// 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.
_40
}

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 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. You can refer to the full list of rate limits here.

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:


_10
-- deletes anonymous users created more than 30 days ago
_10
delete from auth.users
_10
where is_anonymous is true and created_at < now() - interval '30 days';

Resources