mirror of
https://github.com/supabase/supabase.git
synced 2026-10-19 08:09:11 +03:00
Co-authored-by: Kang Ming <kang.ming1996@gmail.com> Co-authored-by: Joel Lee <lee.yi.jie.joel@gmail.com>
282 lines
7.9 KiB
Plaintext
282 lines
7.9 KiB
Plaintext
---
|
|
title: 'Passwordless email logins'
|
|
subtitle: 'Email logins using Magic Links or One-Time Passwords (OTPs)'
|
|
---
|
|
|
|
Supabase Auth provides several passwordless login methods. Passwordless logins allow users to sign in without a password, by clicking a confirmation link or entering a verification code.
|
|
|
|
Passwordless login can:
|
|
|
|
- Improve the user experience by not requiring users to create and remember a password
|
|
- Increase security by reducing the risk of password-related security breaches
|
|
- Reduce support burden of dealing with password resets and other password-related flows
|
|
|
|
Supabase Auth offers two passwordless login methods that use the user's email address:
|
|
|
|
- [Magic Link](#with-magic-link)
|
|
- [OTP](#with-otp)
|
|
|
|
## With Magic Link
|
|
|
|
Magic Links are a form of passwordless login 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.
|
|
|
|
### Enabling Magic Link
|
|
|
|
Email authentication methods, including Magic Links, are enabled by default.
|
|
|
|
Configure the Site URL and any additional redirect URLs. These are the only URLs that are allowed as redirect destinations after the user clicks a Magic Link. You can change the URLs on the [Auth Providers page](/dashboard/project/_/auth/providers) for hosted projects, or in the [configuration file](/docs/guides/cli/config#auth.additional_redirect_urls) for self-hosted projects.
|
|
|
|
By default, a user can only request a magic link once every <SharedData data="config">auth.rate_limits.magic_link.period</SharedData> and they expire after <SharedData data="config">auth.rate_limits.magic_link.validity</SharedData>.
|
|
|
|
### Signing in with Magic Link
|
|
|
|
Call the "sign in with OTP" method from the client library.
|
|
|
|
Though the method is labelled "OTP", it sends a Magic Link by default. The two methods differ only in the content of the confirmation email sent to the user.
|
|
|
|
If the user hasn't signed up yet, they are automatically signed up by default. To prevent this, set the `shouldCreateUser` option to `false`.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```js
|
|
async function signInWithEmail() {
|
|
const { data, error } = await supabase.auth.signInWithOtp({
|
|
email: 'example@email.com',
|
|
options: {
|
|
// set this to false if you do not want the user to be automatically signed up
|
|
shouldCreateUser: false,
|
|
emailRedirectTo: 'https://example.com/welcome',
|
|
},
|
|
})
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="react-native" label="Expo React Native">
|
|
|
|
```ts
|
|
import { makeRedirectUri } from 'expo-auth-session'
|
|
|
|
const redirectTo = makeRedirectUri()
|
|
|
|
const { error } = await supabase.auth.signInWithOtp({
|
|
email: 'example@email.com',
|
|
options: {
|
|
emailRedirectTo: redirectTo,
|
|
},
|
|
})
|
|
```
|
|
|
|
Read the [Deep Linking Documentation](/docs/guides/auth/native-mobile-deep-linking) to learn how to handle deep linking.
|
|
|
|
</TabPanel>
|
|
<TabPanel id="dart" label="Dart">
|
|
|
|
```dart
|
|
Future<void> signInWithEmail() async {
|
|
final AuthResponse res = await supabase.auth.signinwithotp(email: 'example@email.com');
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
```swift
|
|
try await supabase.auth.signInWithOTP(
|
|
email: "example@email.com",
|
|
redirectTo: URL(string: "https://example.com/welcome"),
|
|
// set this to false if you do not want the user to be automatically signed up
|
|
shouldCreateUser: false
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
```kotlin
|
|
suspend fun signInWithEmail() {
|
|
supabase.auth.signInWith(OTP) {
|
|
email = "example@email.com"
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
That's it for the implicit flow.
|
|
|
|
If you're using PKCE flow, edit the Magic Link [email template](/docs/guides/auth/auth-email-templates) to send a token hash:
|
|
|
|
```html
|
|
<h2>Magic Link</h2>
|
|
|
|
<p>Follow this link to login:</p>
|
|
<p><a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=magiclink">Log In</a></p>
|
|
```
|
|
|
|
At the `/auth/confirm` endpoint, exchange the hash for the session:
|
|
|
|
```js
|
|
const { error } = await supabase.auth.verifyOtp({ token_hash, type })
|
|
```
|
|
|
|
## With OTP
|
|
|
|
Email one-time passwords (OTP) are a form of passwordless login where users key in a six digit code sent to their email address to log in to their accounts.
|
|
|
|
### Enabling Email OTP
|
|
|
|
Email authentication methods, including Email OTPs, are enabled by default.
|
|
|
|
Email OTPs share an implementation with Magic Links. To send an OTP instead of a Magic Link, alter the **Magic Link** email template. For a hosted Supabase project, go to [Email Templates](/dashboard/project/_/auth/templates) in the Dashboard. For a self-hosted project or local development, see the [Email Templates guide](/docs/guides/auth/auth-email-templates).
|
|
|
|
Modify the template to include the `{{ .Token }}` variable, for example:
|
|
|
|
```html
|
|
<h2>One time login code</h2>
|
|
|
|
<p>Please enter this code: {{ .Token }}</p>
|
|
```
|
|
|
|
By default, a user can only request an OTP once every <SharedData data="config">auth.rate_limits.otp.period</SharedData> and they expire after <SharedData data="config">auth.rate_limits.otp.validity</SharedData>.
|
|
|
|
### Signing in with Email OTP
|
|
|
|
#### Step 1: Send the user an OTP code
|
|
|
|
Get the user's email and call the "sign in with OTP" method from your client library.
|
|
|
|
If the user hasn't signed up yet, they are automatically signed up by default. To prevent this, set the `shouldCreateUser` option to `false`.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```js
|
|
const { data, error } = await supabase.auth.signInWithOtp({
|
|
email: 'example@email.com',
|
|
options: {
|
|
// set this to false if you do not want the user to be automatically signed up
|
|
shouldCreateUser: false,
|
|
},
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="dart" label="Dart">
|
|
|
|
```dart
|
|
Future<void> signInWithEmailOtp() async {
|
|
final AuthResponse res = await supabase.auth.signInWithOtp(email: 'example@email.com');
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
```swift
|
|
try await supabase.auth.signInWithOTP(
|
|
email: "example@email.com",
|
|
// set this to false if you do not want the user to be automatically signed up
|
|
shouldCreateUser: false
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
```kotlin
|
|
suspend fun signInWithEmailOtp() {
|
|
supabase.auth.signInWith(OTP) {
|
|
email = "example@email.com"
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
If the request is successful, you receive a response with `error: null` and a `data` object where both `user` and `session` are null. Let the user know to check their email inbox.
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"user": null,
|
|
"session": null
|
|
},
|
|
"error": null
|
|
}
|
|
```
|
|
|
|
#### Step 2: Verify the OTP to create a session
|
|
|
|
Provide an input field for the user to enter their one-time code.
|
|
|
|
Call the "verify OTP" method from your client library with the user's email address, the code, and a type of `email`:
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```js
|
|
const {
|
|
data: { session },
|
|
error,
|
|
} = await supabase.auth.verifyOtp({
|
|
email,
|
|
token: '123456',
|
|
type: 'email',
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
```swift
|
|
try await supabase.auth.verifyOTP(
|
|
email: email,
|
|
token: "123456",
|
|
type: .email
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
```kotlin
|
|
supabase.auth.verifyEmailOtp(type = OtpType.Email.EMAIL, email = "email", token = "151345")
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
If successful, the user is now logged in, and you receive a valid session that looks like:
|
|
|
|
```json
|
|
{
|
|
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjI3MjkxNTc3LCJzdWIiOiJmYTA2NTQ1Zi1kYmI1LTQxY2EtYjk1NC1kOGUyOTg4YzcxOTEiLCJlbWFpbCI6IiIsInBob25lIjoiNjU4NzUyMjAyOSIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6InBob25lIn0sInVzZXJfbWV0YWRhdGEiOnt9LCJyb2xlIjoiYXV0aGVudGljYXRlZCJ9.1BqRi0NbS_yr1f6hnr4q3s1ylMR3c1vkiJ4e_N55dhM",
|
|
"token_type": "bearer",
|
|
"expires_in": 3600,
|
|
"refresh_token": "LSp8LglPPvf0DxGMSj-vaQ",
|
|
"user": {...}
|
|
}
|
|
```
|