Files
supabase/apps/docs/content/guides/auth/passwords.mdx
fadymak 0939174a92 feat(docs): use publishable keys instead of anon keys in Auth guides (#44851)
With the upcoming deprecation of the anonymous and service role keys,
this PR updates the Auth guides to use the publishable key instead of
the soon-to-be-deprecated anonymous key.

It also standardizes the example strings to be:
`'https://your-project-id.supabase.co'` and `'sb_publishable_...'` for
consistency.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Standardized client initialization examples to use a consistent
publishable-key placeholder (`sb_publishable_...`) and full project URL
format.
* Replaced "anon key" wording with "publishable key" across auth and API
guides and examples.
* Minor formatting and import-order/whitespace improvements in code
samples for clarity and consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-15 14:13:23 +02:00

1398 lines
37 KiB
Plaintext

---
title: 'Password-based Auth'
subtitle: 'Allow users to sign in with a password connected to their email or phone number.'
---
Users often expect to sign in to your site with a password. Supabase Auth helps you implement password-based auth safely, using secure configuration options and best practices for storing and verifying passwords.
Users can associate a password with their identity using their [email address](#with-email) or a [phone number](#with-phone).
## With email
### Enabling email and password-based authentication
Email authentication is enabled by default.
You can configure whether users need to verify their email to sign in. On hosted Supabase projects, this is true by default. On self-hosted projects or in local development, this is false by default.
Change this setting on the [Auth Providers page](/dashboard/project/_/auth/providers) for hosted projects, or in the [configuration file](/docs/guides/cli/config#auth.email.enable_confirmations) for self-hosted projects.
### Signing up with an email and password
There are two possible flows for email signup: [implicit flow](/docs/guides/auth/sessions#implicit-flow) and [PKCE flow](/docs/guides/auth/sessions#pkce-flow). If you're using SSR, you're using the PKCE flow. If you're using client-only code, the default flow depends upon the client library. The implicit flow is the default in JavaScript and Dart, and the PKCE flow is the default in Swift.
The instructions in this section assume that email confirmations are enabled.
<Tabs
scrollable
stickyTabList={{
style: {
top: 'var(--header-height)',
backgroundColor: 'hsl(var(--background-default) / var(--tw-bg-opacity))',
maskImage: 'none',
borderBottom: '1px solid hsl(var(--border-default) / 1)',
}
}}
size="large"
type="underlined"
queryGroup="flow"
>
<TabPanel id="implicit" label="Implicit flow">
The implicit flow only works for client-only apps. Your site directly receives the access token after the user confirms their email.
<Tabs
scrollable
size="small"
type="underlined"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
To sign up the user, call [signUp()](/docs/reference/javascript/auth-signup) with their email address and password.
You can optionally specify a URL to redirect to after the user clicks the confirmation link. This URL must be configured as a [Redirect URL](/docs/guides/auth/redirect-urls), which you can do in the [dashboard](/dashboard/project/_/auth/url-configuration) for hosted projects, or in the [configuration file](/docs/guides/cli/config#auth.additional_redirect_urls) for self-hosted projects.
If you don't specify a redirect URL, the user is automatically redirected to your site URL. This defaults to `localhost:3000`, but you can also configure this.
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')
// ---cut---
async function signUpNewUser() {
const { data, error } = await supabase.auth.signUp({
email: 'valid.email@supabase.io',
password: 'example-password',
options: {
emailRedirectTo: 'https://example.com/welcome',
},
})
}
```
</TabPanel>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
To sign up the user, call [signUp()](/docs/reference/dart/auth-signup) with their email address and password:
```dart
Future<void> signUpNewUser() async {
final AuthResponse res = await supabase.auth.signUp(
email: 'valid.email@supabase.io',
password: 'example-password'
);
}
```
</TabPanel>
</$Show>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
To sign up the user, call [signUp()](/docs/reference/swift/auth-signup) with their email address and password.
You can optionally specify a URL to redirect to after the user clicks the confirmation link. This URL must be configured as a [Redirect URL](/docs/guides/auth/redirect-urls), which you can do in the [dashboard](/dashboard/project/_/auth/url-configuration) for hosted projects, or in the [configuration file](/docs/guides/cli/config#auth.additional_redirect_urls) for self-hosted projects.
If you don't specify a redirect URL, the user is automatically redirected to your site URL. This defaults to `localhost:3000`, but you can also configure this.
```swift
let response = try await supabase.auth.signUp(
email: "valid.email@supabase.io",
password: "example-password",
redirectTo: URL(string: "https://example.com/welcome")
)
```
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
To sign up the user, call [signUpWith(Email)](/docs/reference/kotlin/auth-signup) with their email address and password:
```kotlin
suspend fun signUpNewUser() {
supabase.auth.signUpWith(Email) {
email = "valid.email@supabase.io"
password = "example-password"
}
}
```
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
To sign up the user, call [signUp()](/docs/reference/python/auth-signup) with their email address and password.
You can optionally specify a URL to redirect to after the user clicks the confirmation link. This URL must be configured as a [Redirect URL](/docs/guides/auth/redirect-urls), which you can do in the [dashboard](/dashboard/project/_/auth/url-configuration) for hosted projects, or in the [configuration file](/docs/guides/cli/config#auth.additional_redirect_urls) for self-hosted projects.
If you don't specify a redirect URL, the user is automatically redirected to your site URL. This defaults to `localhost:3000`, but you can also configure this.
```python
data = await supabase.auth.sign_up({
'email': 'valid.email@supabase.io',
'password': 'example-password',
'options': {
'email_redirect_to': 'https://example.com/welcome',
},
})
```
</TabPanel>
</$Show>
</Tabs>
</TabPanel>
<TabPanel id="pkce" label="PKCE flow">
The PKCE flow allows for server-side authentication. Unlike the implicit flow, which directly provides your app with the access token after the user clicks the confirmation link, the PKCE flow requires an intermediate token exchange step before you can get the access token.
##### Step 1: Update signup confirmation email
Update your signup email template to send the token hash. For detailed instructions on how to configure your email templates, including the use of variables like `{{ .SiteURL }}`, `{{ .TokenHash }}`, and `{{ .RedirectTo }}`, refer to our [Email Templates](/docs/guides/auth/auth-email-templates) guide.
Your signup email template should contain the following HTML:
```html
<h2>Confirm your signup</h2>
<p>Follow this link to confirm your user:</p>
<p>
<a
href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email&next={{ .RedirectTo }}"
>Confirm your email</a
>
</p>
```
##### Step 2: Create token exchange endpoint
Create an API endpoint at `<YOUR_SITE_URL>/auth/confirm` to handle the token exchange.
<$Partial path="create_client_snippet.mdx" />
<Tabs scrollable size="small" type="underlined" defaultActiveId="nextjs" queryGroup="framework">
<TabPanel id="nextjs" label="Next.js">
Create a new file at `app/auth/confirm/route.ts` and populate with the following:
```ts app/auth/confirm/route.ts
import { type EmailOtpType } from '@supabase/supabase-js'
import { redirect } from 'next/navigation'
import { type NextRequest } from 'next/server'
import { createClient } from '@/utils/supabase/server'
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const token_hash = searchParams.get('token_hash')
const type = searchParams.get('type') as EmailOtpType | null
const next = searchParams.get('next') ?? '/'
if (token_hash && type) {
const supabase = await createClient()
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
// redirect user to specified redirect URL or root of app
redirect(next)
}
}
// redirect the user to an error page with some instructions
redirect('/auth/auth-code-error')
}
```
</TabPanel>
<TabPanel id="sveltekit" label="SvelteKit">
Create a new file at `src/routes/auth/confirm/+server.ts` and populate with the following:
```ts src/routes/auth/confirm/+server.ts
import { type EmailOtpType } from '@supabase/supabase-js'
import { redirect } from '@sveltejs/kit'
export const GET = async (event) => {
const {
url,
locals: { supabase },
} = event
const token_hash = url.searchParams.get('token_hash') as string
const type = url.searchParams.get('type') as EmailOtpType | null
const next = url.searchParams.get('next') ?? '/'
/**
* Clean up the redirect URL by deleting the Auth flow parameters.
*
* `next` is preserved for now, because it's needed in the error case.
*/
const redirectTo = new URL(url)
redirectTo.pathname = next
redirectTo.searchParams.delete('token_hash')
redirectTo.searchParams.delete('type')
if (token_hash && type) {
const { error } = await supabase.auth.verifyOtp({ token_hash, type })
if (!error) {
redirectTo.searchParams.delete('next')
redirect(303, redirectTo)
}
}
// return the user to an error page with some instructions
redirectTo.pathname = '/auth/error'
redirect(303, redirectTo)
}
```
</TabPanel>
<TabPanel id="astro" label="Astro">
Create a new file at `src/pages/auth/confirm.ts` and populate with the following:
```ts src/pages/auth/confirm.ts
import { createServerClient, parseCookieHeader } from '@supabase/ssr'
import { type EmailOtpType } from '@supabase/supabase-js'
import { type APIRoute } from 'astro'
export const GET: APIRoute = async ({ request, cookies, redirect }) => {
const requestUrl = new URL(request.url)
const token_hash = requestUrl.searchParams.get('token_hash')
const type = requestUrl.searchParams.get('type') as EmailOtpType | null
const next = requestUrl.searchParams.get('next') || '/'
if (token_hash && type) {
const supabase = createServerClient(
import.meta.env.PUBLIC_SUPABASE_URL,
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
{
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet, _headers) {
cookiesToSet.forEach(({ name, value, options }) => cookies.set(name, value, options))
},
},
}
)
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
return redirect(next)
}
}
// return the user to an error page with some instructions
return redirect('/auth/auth-code-error')
}
```
</TabPanel>
<TabPanel id="remix" label="Remix">
Create a new file at `app/routes/auth.confirm.tsx` and populate with the following:
```ts app/routes/auth.confirm.tsx
import { redirect, type LoaderFunctionArgs } from '@remix-run/node'
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
import { type EmailOtpType } from '@supabase/supabase-js'
export async function loader({ request }: LoaderFunctionArgs) {
const requestUrl = new URL(request.url)
const token_hash = requestUrl.searchParams.get('token_hash')
const type = requestUrl.searchParams.get('type') as EmailOtpType | null
const next = requestUrl.searchParams.get('next') || '/'
const headers = new Headers()
if (token_hash && type) {
const supabase = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(key, value, options) {
headers.append('Set-Cookie', serializeCookieHeader(key, value, options))
},
},
}
)
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
return redirect(next, { headers })
}
}
// return the user to an error page with instructions
return redirect('/auth/auth-code-error', { headers })
}
```
</TabPanel>
<TabPanel id="express" label="Express">
Create a new route in your express app and populate with the following:
```js app.js
// The client you created from the Server-Side Auth instructions
const { createClient } = require("./lib/supabase")
...
app.get("/auth/confirm", async function (req, res) {
const token_hash = req.query.token_hash
const type = req.query.type
const next = req.query.next ?? "/"
if (token_hash && type) {
const supabase = createClient({ req, res })
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
res.redirect(303, `/${next.slice(1)}`)
}
}
// return the user to an error page with some instructions
res.redirect(303, '/auth/auth-code-error')
})
```
</TabPanel>
</Tabs>
##### Step 3: Call the sign up function to initiate the flow
<Tabs
scrollable
size="small"
type="underlined"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
To sign up the user, call [signUp()](/docs/reference/javascript/auth-signup) with their email address and password:
You can optionally specify a URL to redirect to after the user clicks the confirmation link. This URL must be configured as a [Redirect URL](/docs/guides/auth/redirect-urls), which you can do in the [dashboard](/dashboard/project/_/auth/url-configuration) for hosted projects, or in the [configuration file](/docs/guides/cli/config#auth.additional_redirect_urls) for self-hosted projects.
If you don't specify a redirect URL, the user is automatically redirected to your site URL. This defaults to `localhost:3000`, but you can also configure this.
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')
// ---cut---
async function signUpNewUser() {
const { data, error } = await supabase.auth.signUp({
email: 'valid.email@supabase.io',
password: 'example-password',
options: {
emailRedirectTo: 'https://example.com/welcome',
},
})
}
```
</TabPanel>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
To sign up the user, call [signUp()](/docs/reference/dart/auth-signup) with their email address and password:
```dart
Future<void> signUpNewUser() async {
final AuthResponse res = await supabase.auth.signUp(
email: 'valid.email@supabase.io',
password: 'example-password'
);
}
```
</TabPanel>
</$Show>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
To sign up the user, call [signUp()](/docs/reference/swift/auth-signup) with their email address and password:
```swift
let response = try await supabase.auth.signUp(
email: "valid.email@supabase.io",
password: "example-password",
)
```
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
To sign up the user, call [signUpWith(Email)](/docs/reference/kotlin/auth-signup) with their email address and password:
```kotlin
suspend fun signUpNewUser() {
supabase.auth.signUpWith(Email) {
email = "valid.email@supabase.io"
password = "example-password"
}
}
```
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
To sign up the user, call [signUp()](/docs/reference/python/auth-signup) with their email address and password:
```python
data = supabase.auth.sign_up({
'email': 'valid.email@supabase.io',
'password': 'example-password',
})
```
</TabPanel>
</$Show>
</Tabs>
</TabPanel>
</Tabs>
### Signing in with an email and password
<Tabs
scrollable
size="small"
type="underlined"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
When your user signs in, call [`signInWithPassword()`](/docs/reference/javascript/auth-signinwithpassword) with their email address and password:
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')
// ---cut---
async function signInWithEmail() {
const { data, error } = await supabase.auth.signInWithPassword({
email: 'valid.email@supabase.io',
password: 'example-password',
})
}
```
</TabPanel>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
When your user signs in, call [`signInWithPassword()`](/docs/reference/dart/auth-signinwithpassword) with their email address and password:
```dart
Future<void> signInWithEmail() async {
final AuthResponse res = await supabase.auth.signInWithPassword(
email: 'valid.email@supabase.io',
password: 'example-password'
);
}
```
</TabPanel>
</$Show>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
When your user signs in, call [signIn(email:password:)](/docs/reference/swift/auth-signinwithpassword) with their email address and password:
```swift
try await supabase.auth.signIn(
email: "valid.email@supabase.io",
password: "example-password"
)
```
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
When your user signs in, call [signInWith(Email)](/docs/reference/kotlin/auth-signinwithpassword) with their email address and password:
```kotlin
suspend fun signInWithEmail() {
supabase.auth.signInWith(Email) {
email = "valid.email@supabase.io"
password = "example-password"
}
}
```
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
When your user signs in, call [sign_in_with_password()](/docs/reference/python/auth-signinwithpassword) with their email address and password:
```python
data = client.auth.sign_in_with_password({
'email': 'valid.email@supabase.io',
'password': 'example-password',
})
```
</TabPanel>
</$Show>
</Tabs>
### Resetting a password
<Tabs
scrollable
size="small"
type="underlined"
queryGroup="flow"
>
<TabPanel id="implicit" label="Implicit flow">
#### Step 1: Create a reset password page
Create a **reset password** page. This page should be publicly accessible.
Collect the user's email address and request a password reset email. Specify the redirect URL, which should point to the URL of a **change password** page. This URL needs to be configured in your [redirect URLs](/docs/guides/auth/redirect-urls).
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')
// ---cut---
await supabase.auth.resetPasswordForEmail('valid.email@supabase.io', {
redirectTo: 'http://example.com/account/update-password',
})
```
</TabPanel>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
```swift
try await supabase.auth.resetPasswordForEmail(
"valid.email@supabase.io",
redirectTo: URL(string: "http://example.com/account/update-password")
)
```
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
```kotlin
supabase.auth.resetPasswordForEmail(
email = "valid.email@supabase.io",
redirectUrl = "http://example.com/account/update-password"
)
```
If you are on one of the Kotlin targets that have built-in support for redirect URL handling, such as Android, see [OAuth and OTP link verification](/docs/reference/kotlin/initializing).
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
```python
client.auth.reset_password_email(
'valid.email@supabase.io',
{'redirect_to':'http://example.com/account/update-password'}
)
```
</TabPanel>
</$Show>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
```dart
await supabase.auth.resetPasswordForEmail(
'valid.email@supabase.io',
redirectTo: 'http://example.com/account/update-password',
);
```
</TabPanel>
</$Show>
</Tabs>
#### Step 2: Create a change password page
Create a **change password** page at the URL you specified in the previous step. This page should be accessible only to authenticated users.
Collect the user's new password and call `updateUser` to update their password.
</TabPanel>
<TabPanel id="pkce" label="PKCE flow">
The PKCE flow allows for server-side authentication. Unlike the implicit flow, which directly provides your app with the access token after the user clicks the confirmation link, the PKCE flow requires an intermediate token exchange step before you can get the access token.
##### Step 1: Update reset password email
Update your reset password email template to send the token hash. See [Email Templates](/docs/guides/auth/auth-email-templates) for how to configure your email templates.
Your reset password email template should contain the following HTML:
```html
<h2>Reset Password</h2>
<p>Follow this link to reset the password for your user:</p>
<p>
<a
href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery&next=/account/update-password"
>Reset Password</a
>
</p>
```
##### Step 2: Create token exchange endpoint
Create an API endpoint at `<YOUR_SITE_URL>/auth/confirm` to handle the token exchange.
<$Partial path="create_client_snippet.mdx" />
<Tabs scrollable size="small" type="underlined" defaultActiveId="nextjs" queryGroup="framework">
<TabPanel id="nextjs" label="Next.js">
Create a new file at `app/auth/confirm/route.ts` and populate with the following:
```ts app/auth/confirm/route.ts
import { type EmailOtpType } from '@supabase/supabase-js'
import { cookies } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'
// The client you created from the Server-Side Auth instructions
import { createClient } from '@/utils/supabase/server'
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const token_hash = searchParams.get('token_hash')
const type = searchParams.get('type') as EmailOtpType | null
const next = searchParams.get('next') ?? '/'
const redirectTo = request.nextUrl.clone()
redirectTo.pathname = next
if (token_hash && type) {
const supabase = await createClient()
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
return NextResponse.redirect(redirectTo)
}
}
// return the user to an error page with some instructions
redirectTo.pathname = '/auth/auth-code-error'
return NextResponse.redirect(redirectTo)
}
```
</TabPanel>
<TabPanel id="sveltekit" label="SvelteKit">
Create a new file at `src/routes/auth/confirm/+server.ts` and populate with the following:
```ts src/routes/auth/confirm/+server.ts
import { type EmailOtpType } from '@supabase/supabase-js'
import { redirect } from '@sveltejs/kit'
export const GET = async (event) => {
const {
url,
locals: { supabase },
} = event
const token_hash = url.searchParams.get('token_hash') as string
const type = url.searchParams.get('type') as EmailOtpType | null
const next = url.searchParams.get('next') ?? '/'
/**
* Clean up the redirect URL by deleting the Auth flow parameters.
*
* `next` is preserved for now, because it's needed in the error case.
*/
const redirectTo = new URL(url)
redirectTo.pathname = next
redirectTo.searchParams.delete('token_hash')
redirectTo.searchParams.delete('type')
if (token_hash && type) {
const { error } = await supabase.auth.verifyOtp({ token_hash, type })
if (!error) {
redirectTo.searchParams.delete('next')
redirect(303, redirectTo)
}
}
// return the user to an error page with some instructions
redirectTo.pathname = '/auth/error'
redirect(303, redirectTo)
}
```
</TabPanel>
<TabPanel id="astro" label="Astro">
Create a new file at `src/pages/auth/confirm.ts` and populate with the following:
```ts src/pages/auth/confirm.ts
import { createServerClient, parseCookieHeader } from '@supabase/ssr'
import { type EmailOtpType } from '@supabase/supabase-js'
import { type APIRoute } from 'astro'
export const GET: APIRoute = async ({ request, cookies, redirect }) => {
const requestUrl = new URL(request.url)
const token_hash = requestUrl.searchParams.get('token_hash')
const type = requestUrl.searchParams.get('type') as EmailOtpType | null
const next = requestUrl.searchParams.get('next') || '/'
if (token_hash && type) {
const supabase = createServerClient(
import.meta.env.PUBLIC_SUPABASE_URL,
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
{
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet, _headers) {
cookiesToSet.forEach(({ name, value, options }) => cookies.set(name, value, options))
},
},
}
)
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
return redirect(next)
}
}
// return the user to an error page with some instructions
return redirect('/auth/auth-code-error')
}
```
</TabPanel>
<TabPanel id="remix" label="Remix">
Create a new file at `app/routes/auth.confirm.tsx` and populate with the following:
```ts app/routes/auth.confirm.tsx
import { redirect, type LoaderFunctionArgs } from '@remix-run/node'
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
import { type EmailOtpType } from '@supabase/supabase-js'
export async function loader({ request }: LoaderFunctionArgs) {
const requestUrl = new URL(request.url)
const token_hash = requestUrl.searchParams.get('token_hash')
const type = requestUrl.searchParams.get('type') as EmailOtpType | null
const next = requestUrl.searchParams.get('next') || '/'
const headers = new Headers()
if (token_hash && type) {
const supabase = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(key, value, options) {
headers.append('Set-Cookie', serializeCookieHeader(key, value, options))
},
},
}
)
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
return redirect(next, { headers })
}
}
// return the user to an error page with instructions
return redirect('/auth/auth-code-error', { headers })
}
```
</TabPanel>
<TabPanel id="express" label="Express">
Create a new route in your express app and populate with the following:
```js app.js
// The client you created from the Server-Side Auth instructions
const { createClient } = require("./lib/supabase")
...
app.get("/auth/confirm", async function (req, res) {
const token_hash = req.query.token_hash
const type = req.query.type
const next = req.query.next ?? "/"
if (token_hash && type) {
const supabase = createClient({ req, res })
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
res.redirect(303, `/${next.slice(1)}`)
}
}
// return the user to an error page with some instructions
res.redirect(303, '/auth/auth-code-error')
})
```
</TabPanel>
</Tabs>
##### Step 3: Call the reset password by email function to initiate the flow
<Tabs
scrollable
size="small"
type="underlined"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```js
async function resetPassword() {
const { data, error } = await supabase.auth.resetPasswordForEmail(email)
}
```
</TabPanel>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
```swift
try await supabase.auth.resetPasswordForEmail("valid.email@supabase.io")
```
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
```kotlin
supabase.gotrue.sendRecoveryEmail(
email = "valid.email@supabase.io",
)
```
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
```python
supabase.auth.reset_password_email('valid.email@supabase.io')
```
</TabPanel>
</$Show>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
```dart
await supabase.auth.resetPasswordForEmail('valid.email@supabase.io');
```
</TabPanel>
</$Show>
</Tabs>
Once you have a session, collect the user's new password and call `updateUser` to update their password.
</TabPanel>
</Tabs>
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')
// ---cut---
await supabase.auth.updateUser({ password: 'new_password' })
```
</TabPanel>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
```swift
try await supabase.auth.updateUser(user: UserAttributes(password: newPassword))
```
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
```kotlin
supabase.auth.updateUser {
password = "new_password"
}
```
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
```python
supabase.auth.update_user({'password': 'new_password'})
```
</TabPanel>
</$Show>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
```dart
final UserResponse res = await supabase.auth.updateUser(
UserAttributes(password: 'new_password'),
);
```
</TabPanel>
</$Show>
</Tabs>
#### Verifying the current password
If your app requires users to confirm their current password before setting a new one, you can pass `currentPassword` (available in `supabase-js` v2.102.0+ and `supabase-kt` 3.5.0+):
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```js
await supabase.auth.updateUser({
password: 'new_password',
currentPassword: 'old_password',
})
```
</TabPanel>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
```kotlin
supabase.auth.updateUser {
password = "new_password"
currentPassword = "old_password"
}
```
</TabPanel>
</$Show>
</Tabs>
### Email sending
The signup confirmation and password reset flows require an SMTP server to send emails.
The Supabase platform comes with a default email-sending service for you to try out. The service has a rate limit of <SharedData data="config">auth.rate_limits.email.inbuilt_smtp_per_hour.value</SharedData> emails per hour, and availability is on a best-effort basis. For production use, you should consider configuring a custom SMTP server.
<Admonition type="tip">
Consider configuring a custom SMTP server for production.
</Admonition>
See the [Custom SMTP guide](/docs/guides/auth/auth-smtp) for instructions.
#### Local development with Mailpit
You can test email flows on your local machine. The Supabase CLI automatically captures emails sent locally by using [Mailpit](https://github.com/axllent/mailpit).
In your terminal, run `supabase status` to get the Mailpit URL. Go to this URL in your browser, and follow the instructions to find your emails.
## With phone
You can use a user's mobile phone number as an identifier, instead of an email address, when they sign up with a password.
This practice is usually discouraged because phone networks recycle mobile phone numbers. Anyone receiving a recycled phone number gets access to the original user's account. To mitigate this risk, [implement MFA](/docs/guides/auth/auth-mfa).
<Admonition type="danger">
Protect users who use a phone number as a password-based auth identifier by enabling MFA.
</Admonition>
### Enabling phone and password-based authentication
Enable phone authentication on the [Auth Providers page](/dashboard/project/_/auth/providers) for hosted Supabase projects.
For self-hosted projects or local development, use the [configuration file](/docs/guides/cli/config#auth.sms.enable_signup). See the configuration variables namespaced under `auth.sms`.
If you want users to confirm their phone number on signup, you need to set up an SMS provider. Each provider has its own configuration. Supported providers include MessageBird, Twilio, Vonage, and TextLocal (community-supported).
<AuthSmsProviderConfig />
### Signing up with a phone number and password
To sign up the user, call [`signUp()`](/docs/reference/javascript/auth-signup) with their phone number and password:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```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.signUp({
phone: '+13334445555',
password: 'some-password',
})
```
</TabPanel>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
```swift
try await supabase.auth.signUp(
phone: "+13334445555",
password: "some-password"
)
```
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
```kotlin
supabase.auth.signUpWith(Phone) {
phone = "+13334445555"
password = "some-password"
}
```
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
```python
supabase.auth.sign_up({
'phone': "+13334445555",
'password': "some-password"
})
```
</TabPanel>
</$Show>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
```dart
final AuthResponse res = await supabase.auth.signUp(
phone: '+13334445555',
password: 'some-password',
);
```
</TabPanel>
</$Show>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/signup' \
-H "apikey: SUPABASE_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+13334445555",
"password": "some-password"
}'
```
</TabPanel>
</Tabs>
If you have phone verification turned on, the user receives an SMS with a 6-digit pin that you must verify within 60 seconds:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOtp`:
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')
// ---cut---
const {
data: { session },
error,
} = await supabase.auth.verifyOtp({
phone: '+13334445555',
token: '123456',
type: 'sms',
})
```
</TabPanel>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOTP`:
```swift
try await supabase.auth.verifyOTP(
phone: "+13334445555",
token: "123456",
type: .sms
)
```
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyPhoneOtp`:
```kotlin
supabase.auth.verifyPhoneOtp(
type = OtpType.Phone.SMS,
phone = "+13334445555",
token = "123456"
)
```
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verify_otp`:
```python
supabase.auth.verify_otp({
'phone': "+13334445555",
'token': "123456",
'type': "sms"
})
```
</TabPanel>
</$Show>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOTP`:
```dart
final AuthResponse res = await supabase.auth.verifyOTP(
phone: '+13334445555',
token: '123456',
type: OtpType.sms,
);
```
</TabPanel>
</$Show>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://<PROJECT_REF>.supabase.co/auth/v1/verify' \
-H "apikey: <SUPABASE_KEY>" \
-H "Content-Type: application/json" \
-d '{
"type": "sms",
"phone": "+13334445555",
"token": "123456"
}'
```
</TabPanel>
</Tabs>
### Signing in a with a phone number and password
Call the function to sign in with the user's phone number and password:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```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.signInWithPassword({
phone: '+13334445555',
password: 'some-password',
})
```
</TabPanel>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
```swift
try await supabase.auth.signIn(
phone: "+13334445555",
password: "some-password"
)
```
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
```kotlin
supabase.auth.signInWith(Phone) {
phone = "+13334445555"
password = "some-password"
}
```
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
```python
supabase.auth.sign_in_with_password({
'phone': "+13334445555",
'password': "some-password"
})
```
</TabPanel>
</$Show>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
```dart
final AuthResponse res = await supabase.auth.signInWithPassword(
phone: '+13334445555',
password: 'some-password',
);
```
</TabPanel>
</$Show>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/token?grant_type=password' \
-H "apikey: SUPABASE_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+13334445555",
"password": "some-password"
}'
```
</TabPanel>
</Tabs>