new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Supabase · Auth · all subjects

authentication/methods

414 notes in this subject, read out of this brain and free to use. This is page 1 of 7.

Auth methods supported by Supabase

Supabase Auth supports multiple authentication methods: password, magic link, one-time password (OTP), social login, and single sign-on (SSO).

Cloudflare Turnstile setup - credentials location

For Cloudflare Turnstile, sign in to the Cloudflare dashboard and create a Turnstile widget by following Cloudflare's Create a widget guide. Once created, copy the Sitekey and Secret Key to use in Supabase.

Cloudflare Turnstile local testing requirement

To test Cloudflare Turnstile locally, add localhost to the domain allowlist as per Cloudflare docs.

hCaptcha setup - credentials location

For hCaptcha, sign up at hCaptcha website. The Sitekey and Secret key are available on the Welcome page when you sign up. If you missed them, the Secret key can be found in the Settings page, and the Sitekey can be found in the Settings of the active site you created under the Sitekey section.

hCaptcha local testing requirement

To test hCaptcha locally, use ngrok or add an entry to your hosts file, as documented in hCaptcha docs.

Cloudflare Turnstile React integration example

Install @marsidev/react-turnstile package. Import Turnstile component, create state for captchaToken, and use the component with siteKey and onSuccess callback: <Turnstile siteKey="your-sitekey" onSuccess={(token) => { setCaptchaToken(token) }} />. Pass captchaToken to supabase.auth.signUp({ email, password, options: { captchaToken } }).

hCaptcha React integration example

Install @hcaptcha/react-hcaptcha package. Import HCaptcha component, create state for captchaToken, and use the component with sitekey and onVerify callback: <HCaptcha ref={captcha} sitekey="your-sitekey" onVerify={(token) => { setCaptchaToken(token) }} />. Pass captchaToken to supabase.auth.signUp({ email, password, options: { captchaToken } }). Reset CAPTCHA after signUp with captcha.current.resetCaptcha().

signUp with captchaToken parameter

The supabase.auth.signUp method accepts a captchaToken in the options object: supabase.auth.signUp({ email, password, options: { captchaToken } }).

Enable CAPTCHA in Supabase dashboard

To enable CAPTCHA protection, navigate to Auth section of Project Settings in the Supabase Dashboard, go to Settings > Authentication > Bot and Abuse Protection > Enable CAPTCHA protection. Select the CAPTCHA provider from a dropdown, enter the Secret key, and click Save.

CAPTCHA providers supported

Supabase authentication supports hCaptcha and Cloudflare Turnstile for protecting sign-in, sign-up, and password reset forms against bots and malicious scripts.

OTP rate limits and expiry configuration

By default, a user can only request an OTP once every auth.rate_limits.otp.period and they expire after auth.rate_limits.otp.validity. This is configurable via Authentication > Sign In / Providers > Auth Providers > Email > Email OTP expiration. An expiry duration of more than 86,400 seconds (one day) is strongly discouraged and can only be set via the Management API.

signInWithOtp response for OTP

When signing in with OTP using signInWithOtp, if the request is successful, you receive a response with error: null and a data object where both user and session are null. The user should be instructed to check their email inbox for the OTP code.

Email OTP expiration affects all email links

The Email OTP Expiration setting also governs the validity of Magic Links and other email links, including confirmation, password recovery, email change, and invitation links.

Magic Link JavaScript example

Example of signing in with Magic Link using JavaScript: ```js import { createClient } from '@supabase/supabase-js' const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...') async function signInWithEmail() { const { data, error } = await supabase.auth.signInWithOtp({ email: 'valid.email@supabase.io', options: { shouldCreateUser: false, emailRedirectTo: 'https://example.com/welcome', }, }) } ```

Session response structure after OTP verification

After successfully verifying an OTP, a session is returned with an access_token (JWT), token_type (bearer), expires_in (in seconds), refresh_token, and user object.

shouldCreateUser option for Magic Link sign-in

When calling signInWithOtp for Magic Link login, if the user hasn't signed up yet, they are automatically signed up by default. To prevent this automatic sign-up, set the shouldCreateUser option to false.

Magic Link enabled by default

Email authentication methods, including Magic Links, are enabled by default in Supabase Auth.

verifyOtp JavaScript example

Example of verifying an OTP and creating a session using JavaScript: ```js import { createClient } from '@supabase/supabase-js' const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...') const { data: { session }, error, } = await supabase.auth.verifyOtp({ email: 'email@example.com', token: '123456', type: 'email', }) ```

verifyOtp method for OTP verification

To verify an OTP, call the verifyOtp method from the client library with the user's email address, the code, and a type of 'email'. This creates a session if the OTP is valid.

OTP send JavaScript example

Example of sending an OTP using JavaScript: ```js import { createClient } from '@supabase/supabase-js' const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...') const { data, error } = await supabase.auth.signInWithOtp({ email: 'valid.email@supabase.io', options: { shouldCreateUser: false, }, }) ```

shouldCreateUser option for OTP sign-in

When calling signInWithOtp for email OTP login, if the user hasn't signed up yet, they are automatically signed up by default. To prevent this automatic sign-up, set the shouldCreateUser option to false.

Magic Link login overview

Magic Links are a passwordless login method where users click on a link sent to their email address to log in to their accounts. Magic Links only work with email addresses and are one-time use only.

Email OTP template configuration

Email OTPs share an implementation with Magic Links. To send an OTP instead of a Magic Link, alter the Magic Link email template and include the {{ .Token }} variable. Example template: ```html <h2>One time login code</h2> <p>Please enter this code: {{ .Token }}</p> ```

Magic Link redirect URL configuration

Configure the Site URL and any additional redirect URLs for Magic Links. These are the only URLs allowed as redirect destinations after the user clicks a Magic Link. URLs can be changed on the URL Configuration page for hosted projects, in the config.toml file for local development, or in the .env configuration file for self-hosted Supabase.

Email OTP enabled by default

Email authentication methods, including Email OTPs, are enabled by default in Supabase Auth.

OTP email login overview

Email one-time passwords (OTP) are a passwordless login method where users enter a six digit code sent to their email address to log in to their accounts.

Verify OTP with token hash for PKCE flow

At the /auth/confirm endpoint for PKCE flow, exchange the token hash for the session using the verifyOtp method with token_hash and type 'email'.

signInWithOtp method sends Magic Link by default

The signInWithOtp method from the client library sends a Magic Link by default. Though the method is labelled OTP, it sends a Magic Link by default and the two methods differ only in the content of the confirmation email sent to the user.

Magic Link rate limits and expiry

By default, a user can only request a magic link once every auth.rate_limits.magic_link.period and they expire after auth.rate_limits.magic_link.validity.

PKCE flow Magic Link email template

For PKCE flow, edit the Magic Link email template to send a token hash using the variable {{ .TokenHash }}. Example template: ```html <h2>Sign in to your account</h2> <p>Use this link to sign in to your account:</p> <p><a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email">Sign in</a></p> ```

linkIdentity() JavaScript example

import { createClient } from '@supabase/supabase-js' const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...') const { data, error } = await supabase.auth.linkIdentity({ provider: 'google' })

updateUser() to link email C# example

var updateEmail = await supabase.Auth.Update(new UserAttributes { 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 var updatePassword = await supabase.Auth.Update(new UserAttributes { Password = "password" });

updateUser() to link email Kotlin example

supabase.auth.updateUser { email = "valid.email@supabase.io" }

updateUser() to link email Swift example

try await supabase.auth.update( user: UserAttributes(email: "valid.email@supabase.io") )

Delete anonymous users older than 30 days

delete from auth.users where is_anonymous is true and created_at < now() - interval '30 days';

updateUser() to link email JavaScript example

import { createClient } from '@supabase/supabase-js' const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...') 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', })

Convert anonymous user to permanent user via email linking

To convert an anonymous user to a permanent user by linking an email identity, use updateUser() with an email address. The user must verify the email by clicking an email change link or entering the 6-digit OTP sent to the email address. Once verified, updateUser() can be called again to set a password. Manual linking must be enabled in the Supabase project.

signInAnonymously() C# example

var session = await supabase.Auth.SignInAnonymously();

signInAnonymously() Kotlin example

supabase.auth.signInAnonymously()

signInAnonymously() Swift example

let session = try await supabase.auth.signInAnonymously()

signInAnonymously() Dart/Flutter example

await supabase.auth.signInAnonymously();

Automatic cleanup of anonymous users not available

Automatic cleanup of anonymous users is currently not available. Anonymous users must be manually deleted from the project using SQL queries.

signInAnonymously() JavaScript example

import { createClient } from '@supabase/supabase-js' const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...') const { data, error } = await supabase.auth.signInAnonymously()

Anonymous user differs from anon API key

An anonymous user created by signInAnonymously() is different from the anon API key. The anon key does not create a user and uses the anonymous Postgres role to implement public database access. Anonymous users use the authenticated Postgres role.

Data conflict resolution strategies for anonymous to permanent conversion

When converting an anonymous user to a permanent user, data conflicts can arise in applications like e-commerce. Possible conflict resolution strategies are: 1) Overwrite items in the cart with those in the existing account, 2) Overwrite items in the cart with those from the anonymous user, or 3) Merge items in the cart together.

Anonymous sign-in creates permanent-like authenticated user

Calling signInAnonymously() creates an anonymous user that behaves like a permanent user, except the user cannot access their account if they sign out, clear browsing data, or use another device. Like permanent users, the authenticated Postgres role is used when accessing the project with Data APIs, and JWTs contain an is_anonymous claim to distinguish anonymous users in RLS policies.

Anonymous sign-in rate limit and abuse prevention

Anonymous users can be abused to increase database size. An IP-based rate limit of 30 requests per hour is enforced by default and can be modified in the dashboard. It is strongly recommended to enable invisible CAPTCHA or Cloudflare Turnstile to prevent abuse for anonymous sign-ins.

Next.js static rendering issue with anonymous users

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.

updateUser() to link email Dart example

await supabase.auth.updateUser(UserAttributes(email: 'valid.email@supabase.io'));

signInAnonymously() Python example

response = supabase.auth.sign_in_anonymously()

Link anonymous user to existing account example

// 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. }

Convert anonymous user to permanent user via OAuth linking

Use linkIdentity() method to link an OAuth identity to an anonymous user. This converts the anonymous user to a permanent user by associating an OAuth provider identity.

Authentication email types

Supabase sends six types of authentication emails: Confirm sign up, Invite user, Magic link or OTP, Change email address, Reset password, and Reauthentication.

Email prefetching mitigation option 2: Custom confirmation page

To guard against email prefetching, create a custom email link that redirects to a page where users can click a button to confirm: <a href="{{ .SiteURL }}/confirm-signup?confirmation_url={{ .ConfirmationURL }}">. The button should contain the actual confirmation link obtained by parsing the confirmation_url query parameter.

Email prefetching mitigation option 1: OTP

To guard against email prefetching, use an email OTP instead by including {{ .Token }} in the email template. Create a custom email link that redirects to a page where users can enter their email and token. Then verify the OTP with supabase.auth.verifyOtp({ email, token, type: 'email' }).

verifyOtp for server-side authentication

The verifyOtp method can be called server-side with token_hash and type parameters to verify email tokens. It makes a POST request to Supabase Auth and returns an authenticated session in the response body that can be read by the server. Usage: await supabase.auth.verifyOtp({ token_hash, type: type as EmailOtpType }).

Before User Created Hook - Output Response Format

The hook must return a JSON response with an optional error field. To allow the signup, return an empty object {} with HTTP 200 status or HTTP 204 No Content with no body. To reject the signup, return a JSON object with an error field containing http_code (integer) and message (string), using a 4xx HTTP status code. The error message is propagated to the client that attempted signup.

Before User Created Hook - Purpose and Behavior

The before-user-created hook runs before a new user is created in Supabase Auth. It allows developers to inspect the incoming user object and optionally reject the request. If the hook returns an error object, the signup is denied and the user is not created. If the hook responds successfully with HTTP 200 or 204 with no error, the request proceeds as usual. Use this hook to enforce custom signup policies that Supabase Auth does not handle natively, such as blocking disposable email domains, restricting access by region or IP, or requiring users to belong to a specific email domain.

Before User Created Hook - Input Payload Structure

The before-user-created hook receives a payload with two top-level fields: metadata (object) containing uuid (string, uuid format), time (string, date-time format), ip_address (string, ipv4 format), and name (string, enum value 'before-user-created'); and user (object) containing id (string, uuid format), aud (string), role (string), email (string, email format), phone (string), app_metadata (object with required fields provider and providers array), user_metadata (object), identities (array of objects), created_at (string, date-time format), updated_at (string, date-time format), and is_anonymous (boolean). All fields in both metadata and user objects are required.

Before User Created Hook - User Not Yet in Database

Because the hook runs immediately before insertion into the database, the user will not be found in Postgres at the time the hook is called.

Give your agent this brain