Clerk logo

Clerk Docs

Ctrl + K
Go to clerkstage.dev

Magic Links

Learn how to authenticate or verify users with magic links.

Overview

Clerk supports passwordless authentication with magic links, which lets users sign in and sign up without having to remember a password. During login or registration, users will be asked to enter their email address to receive an email message with a link that can be clicked and complete the authentication process.

This one-click, link-based verification method is often referred to as a "magic link". The process is similar to sending a one-time code to your users but skipping the part where they have to come back to your app and enter the code. This is where the "magic" kicks in.

As a form of passwordless authentication, magic links arguably provide greater security and a better user experience than traditional passwords. Since there are fewer steps involved in every authentication attempt, the user experience is better than one-time codes. However, magic links are not without their downsides, and often still boil down to the email provider's "knowledge-based factor" instead of yours.

Magic links are the default passwordless authentication strategy when using Clerk. They can be used to sign up new users, sign in existing ones or allow existing users to verify newly entered email addresses to their profile.

Your users will still be able to choose an alternative authentication (or verification) method even after they've clicked the magic link they received in their inbox. Magic links are simply the default authentication method for email address-based, passwordless authentication in Clerk.

Looking for one-time code (OTP) authentication? Check out our one-time code authentication guide.

Magic links can be used to easily authenticate users or verify their email addresses. In all the above cases, Clerk will take care of the plumbing and allow you to offer a seamless experience to your users:

  1. The user enters their email address and asks for a magic link.
  2. Your application waits for the verification result.
  3. Clerk sends an email to the user, containing a link to the verification URL.
  4. The user clicks the magic link. This can happen on the same device where they entered their email address, or on a different device.
  5. Clerk will verify the user's identity and advance any sign-in or sign-up attempt that might be in progress. In case the verification fails, Clerk will inform the user.
  6. Your user will now be logged in on the device or tab that they opened the link.

Magic links work on any device. There's no constraint on where the link will be opened. For example, a user might try to sign in from their desktop browser, but open the link from their mobile phone.

As an additional security measure, we expire magic links after a while. This way we can guard against cases where a stale link might be compromised. From a user experience perspective, the magic link flow is supposed to be nearly synchronous. Don't worry, your users will have plenty of time to complete the flow before the magic link expires.

Clerk provides a highly flexible API that allows you to hook into any of the above steps while abstracting away all the complexities of a magic link-based authentication or verification flow.

We take care of the boring stuff, like efficient polling, secure session management, and different device authentication so you can focus on your application code.

Before you start

Configuration

Magic link authentication can be configured through the Clerk Dashboard. Go to your instance, then User & Authentication > Email, Phone, Username. Simply choose Email verification link as the authentication factor.

authentication dashboard

Don't forget that you also need to make sure you've configured your application instance to request the user's email address. Users can receive magic links only via email messages. Make sure you toggle on Email address under the Contact information section. You can verify that email addresses will be used for identification, by clicking on the cog next to the Email address option and switching on the Used for identification toggle in the modal.

email verification link

Don't forget to click on the Apply Changes button at the bottom of the page once you're done configuring your instance.

That's all you need to do to enable authentication with magic links for your instance.

Custom flow

In case one of the above integration methods doesn't cover your needs, you can make use of lower-level commands and create a completely custom magic link authentication flow.

You still need to configure your instance in order to enable magic link authentication, as described at the top of this guide.

Sign up using a custom flow

Registration with magic links follows a set of steps that require users to enter their email address as an authentication identifier and click on a link that's delivered to them via email message.

The sign-up process can be completed on the same or a different device. For example, users might enter their email address in their desktop browser, but click the sign-up magic link from their mobile phone. The user's email address will still be verified and registration will proceed.

Let's see all the steps involved in more detail.

  • Initiate the sign-up process, by collecting the user's identifier. It must be their email address.
  • Start the magic link verification flow. There are two parts to the flow:
    • Prepare a verification for the email address by sending an email with a magic link to the user.
    • Wait until the magic link is clicked. This is a polling behavior that can be canceled at any time.
  • Handle the magic link verification result accordingly. Note that the magic link can be clicked on a different device/browser than the one which initiated the flow.
    • The verification was successful so you need to continue with the sign-up flow.
    • The verification failed or the magic link has expired.

Clerk provides a highly flexible API that allows you to hook into any of the above steps while abstracting away all the complexities of a magic link-based sign-up flow.

1
import React from "react";
2
import { useRouter } from "next/router";
3
import {
4
MagicLinkErrorCode,
5
isMagicLinkError,
6
useClerk,
7
useSignUp
8
} from "@clerk/nextjs";
9
10
// pages/sign-up.jsx
11
// Render the sign up form.
12
// Collect user's email address and send a magic link with which
13
// they can sign up.
14
function SignUp() {
15
const [emailAddress, setEmailAddress] = React.useState("");
16
const [expired, setExpired] = React.useState(false);
17
const [verified, setVerified] = React.useState(false);
18
const router = useRouter();
19
const { signUp, isLoaded, setSession } = useSignUp();
20
21
if (!isLoaded) {
22
return null;
23
}
24
25
26
const { startMagicLinkFlow, cancelMagicLinkFlow } =
27
signUp.createMagicLinkFlow();
28
29
async function submit(e) {
30
e.preventDefault();
31
setExpired(false);
32
setVerified(false);
33
34
// Start the sign up flow, by collecting
35
// the user's email address.
36
await signUp.create({ emailAddress });
37
38
// Start the magic link flow.
39
// Pass your app URL that users will be navigated
40
// when they click the magic link from their
41
// email inbox.
42
// su will hold the updated sign up object.
43
const su = await startMagicLinkFlow({
44
redirectUrl: "https://your-app.domain.com/verification",
45
});
46
47
// Check the verification result.
48
const verification = su.verifications.emailAddress;
49
if (verification.verifiedFromTheSameClient()) {
50
setVerified(true);
51
// If you're handling the verification result from
52
// another route/component, you should return here.
53
// See the <MagicLinkVerification/> component as an
54
// example below.
55
// If you want to complete the flow on this tab,
56
// don't return. Check the sign up status instead.
57
return;
58
} else if (verification.status === "expired") {
59
setExpired(true);
60
}
61
62
if (su.status === "complete") {
63
// Sign up is complete, we have a session.
64
// Navigate to the after sign up URL.
65
setSession(
66
su.createdSessionId,
67
() => router.push("/after-sign-up-path"),
68
);
69
return;
70
}
71
}
72
73
if (expired) {
74
return (
75
<div>Magic link has expired</div>
76
);
77
}
78
79
if (verified) {
80
return (
81
<div>Signed in on other tab</div>
82
);
83
}
84
85
return (
86
<form onSubmit={submit}>
87
<input
88
type="email"
89
value={emailAddress}
90
onChange={e => setEmailAddress(e.target.value)}
91
/>
92
<button type="submit">
93
Sign up with magic link
94
</button>
95
</form>
96
);
97
}
98
99
// pages/verification.jsx
100
// Handle magic link verification results. This is
101
// the final step in the magic link flow.
102
function Verification() {
103
const [
104
verificationStatus,
105
setVerificationStatus,
106
] = React.useState("loading");
107
108
const { handleMagicLinkVerification } = useClerk();
109
110
React.useEffect(() => {
111
async function verify() {
112
try {
113
await handleMagicLinkVerification({
114
redirectUrl: "https://redirect-to-pending-sign-up",
115
redirectUrlComplete: "https://redirect-when-sign-up-complete",
116
});
117
// If we're not redirected at this point, it means
118
// that the flow has completed on another device.
119
setVerificationStatus("verified");
120
} catch (err) {
121
// Verification has failed.
122
let status = "failed";
123
if (isMagicLinkError(err) && err.code === MagicLinkErrorCode.Expired) {
124
status = "expired";
125
}
126
setVerificationStatus(status);
127
}
128
}
129
verify();
130
}, []);
131
132
if (verificationStatus === "loading") {
133
return <div>Loading...</div>
134
}
135
136
if (verificationStatus === "failed") {
137
return (
138
<div>Magic link verification failed</div>
139
);
140
}
141
142
if (verificationStatus === "expired") {
143
return (
144
<div>Magic link expired</div>
145
);
146
}
147
148
return (
149
<div>
150
Successfully signed up. Return to the original tab to continue.
151
</div>
152
);
153
}
1
import React from "react";
2
import {
3
BrowserRouter as Router,
4
Routes,
5
Route,
6
useNavigate,
7
} from 'react-router-dom';
8
import {
9
ClerkProvider,
10
ClerkLoaded,
11
MagicLinkErrorCode,
12
isMagicLinkError,
13
UserButton,
14
useClerk,
15
useSignUp,
16
SignedOut,
17
SignedIn,
18
} from '@clerk/clerk-react';
19
20
const frontendApi = process.env.REACT_APP_CLERK_FRONTEND_API;
21
22
function App() {
23
return (
24
<Router>
25
<ClerkProvider frontendApi={frontendApi}>
26
<Switch>
27
{/* Root path shows sign up page. */}
28
<Route
29
path="/"
30
element={
31
<>
32
<SignedOut>
33
<SignUpMagicLink />
34
</SignedOut>
35
<SignedIn>
36
<UserButton afterSignOutAllUrl="/" />
37
</SignedIn>
38
</>
39
}
40
/>
41
42
{/* Define a /verification route that handles magic link result */}
43
<Route
44
path="/verification"
45
element={
46
<ClerkLoaded>
47
<MagicLinkVerification />
48
</ClerkLoaded>
49
}
50
/>
51
</Routes>
52
</ClerkProvider>
53
</Router>
54
);
55
}
56
57
// Render the sign up form.
58
// Collect user's email address and send a magic link with which
59
// they can sign up.
60
function SignUpMagicLink() {
61
const [emailAddress, setEmailAddress] = React.useState("");
62
const [expired, setExpired] = React.useState(false);
63
const [verified, setVerified] = React.useState(false);
64
const navigate = useNavigate();
65
const { signUp, isLoaded, setSession } = useSignUp();
66
67
if (!isLoaded) {
68
return null;
69
}
70
71
72
const { startMagicLinkFlow, cancelMagicLinkFlow } =
73
signUp.createMagicLinkFlow();
74
75
async function submit(e) {
76
e.preventDefault();
77
setExpired(false);
78
setVerified(false);
79
80
// Start the sign up flow, by collecting
81
// the user's email address.
82
await signUp.create({ emailAddress });
83
84
// Start the magic link flow.
85
// Pass your app URL that users will be navigated
86
// when they click the magic link from their
87
// email inbox.
88
// su will hold the updated sign up object.
89
const su = await startMagicLinkFlow({
90
redirectUrl: "https://your-app.domain.com/verification",
91
});
92
93
// Check the verification result.
94
const verification = su.verifications.emailAddress;
95
if (verification.verifiedFromTheSameClient()) {
96
setVerified(true);
97
// If you're handling the verification result from
98
// another route/component, you should return here.
99
// See the <MagicLinkVerification/> component as an
100
// example below.
101
// If you want to complete the flow on this tab,
102
// don't return. Check the sign up status instead.
103
return;
104
} else if (verification.status === "expired") {
105
setExpired(true);
106
}
107
108
if (su.status === "complete") {
109
// Sign up is complete, we have a session.
110
// Navigate to the after sign up URL.
111
setSession(
112
su.createdSessionId,
113
() => navigate("/after-sign-up-path"),
114
);
115
return;
116
}
117
}
118
119
if (expired) {
120
return (
121
<div>Magic link has expired</div>
122
);
123
}
124
125
if (verified) {
126
return (
127
<div>Signed in on other tab</div>
128
);
129
}
130
131
return (
132
<form onSubmit={submit}>
133
<input
134
type="email"
135
value={emailAddress}
136
onChange={e => setEmailAddress(e.target.value)}
137
/>
138
<button type="submit">
139
Sign up with magic link
140
</button>
141
</form>
142
);
143
}
144
145
// Handle magic link verification results. This is
146
// the final step in the magic link flow.
147
function MagicLinkVerification() {
148
const [
149
verificationStatus,
150
setVerificationStatus,
151
] = React.useState("loading");
152
153
const { handleMagicLinkVerification } = useClerk();
154
155
React.useEffect(() => {
156
async function verify() {
157
try {
158
await handleMagicLinkVerification({
159
redirectUrl: "https://redirect-to-pending-sign-up",
160
redirectUrlComplete: "https://redirect-when-sign-up-complete",
161
});
162
// If we're not redirected at this point, it means
163
// that the flow has completed on another device.
164
setVerificationStatus("verified");
165
} catch (err) {
166
// Verification has failed.
167
let status = "failed";
168
if (isMagicLinkError(err) && err.code === MagicLinkErrorCode.Expired) {
169
status = "expired";
170
}
171
setVerificationStatus(status);
172
}
173
}
174
verify();
175
}, []);
176
177
if (verificationStatus === "loading") {
178
return <div>Loading...</div>
179
}
180
181
if (verificationStatus === "failed") {
182
return (
183
<div>Magic link verification failed</div>
184
);
185
}
186
187
if (verificationStatus === "expired") {
188
return (
189
<div>Magic link expired</div>
190
);
191
}
192
193
return (
194
<div>
195
Successfully signed up. Return to the original tab to continue.
196
</div>
197
);
198
}
199
200
export default App;
1
const signUp = window.Clerk.client.signUp;
2
const {
3
startMagicLinkFlow,
4
cancelMagicLinkFlow,
5
} = signUp.createMagicLinkFlow();
6
7
const res = await startMagicLinkFlow({
8
// Pass your app URL that users will be navigated
9
// when they click the magic link from their
10
// email inbox.
11
redirectUrl: "https://redirect-from-email-magic-link"
12
});
13
if (res.status === "completed") {
14
// sign up completed
15
} else {
16
// sign up still pending
17
}
18
// Cleanup
19
cancelMagicLinkFlow();

Sign in using a custom flow

Signing users into your application is probably the most popular use case for magic links. Users enter their email address and then click on a link that's delivered to them via email message in order to log in.

The sign-in process can be completed on the same or a different device. For example, users might enter their email address in their desktop browser, but click the sign-in magic link from their mobile phone. The user's email address will still be verified and authentication will proceed.

Let's see all the steps involved in more detail.

  • Initiate the sign-in process, by collecting the user's authentication identifier. It must be their email address.
  • Start the magic link verification flow. There are two parts to the flow:
    • Prepare a verification for the email address by sending an email with a magic link to the user.
    • Wait until the magic link is clicked. This is a polling behavior that can be canceled at any time.
  • Handle the magic link verification result accordingly. Note that the magic link can be clicked on a different device/browser than the one which initiated the flow.
    • The verification was successful so you need to continue with the sign-in flow.
    • The verification failed or the magic link has expired.

Clerk provides a highly flexible API that allows you to hook into any of the above steps, while abstracting away all the complexities of a magic link based sign in flow.

1
import React from "react";
2
import { useRouter } from "next/router";
3
import {
4
MagicLinkErrorCode,
5
isMagicLinkError,
6
useClerk,
7
useSignIn,
8
} from "@clerk/nextjs";
9
10
// pages/sign-in.jsx
11
// Render the sign in form.
12
// Collect user's email address and send a magic link with which
13
// they can sign in.
14
function SignIn() {
15
const [emailAddress, setEmailAddress] = React.useState("");
16
const [expired, setExpired] = React.useState(false);
17
const [verified, setVerified] = React.useState(false);
18
const router = useRouter();
19
const { signIn, isLoaded, setSession } = useSignIn();
20
21
if (!isLoaded) {
22
return null;
23
}
24
25
const { startMagicLinkFlow, cancelMagicLinkFlow } = signIn.createMagicLinkFlow();
26
27
async function submit(e) {
28
e.preventDefault();
29
setExpired(false);
30
setVerified(false);
31
32
// Start the sign in flow, by collecting
33
// the user's email address.
34
const si = await signIn.create({ identifier: emailAddress });
35
const { emailAddressId } = si.supportedFirstFactors.find(
36
ff => ff.strategy === "email_link" && ff.safeIdentifier === emailAddress
37
);
38
39
// Start the magic link flow.
40
// Pass your app URL that users will be navigated
41
// res will hold the updated sign in object.
42
const res = await startMagicLinkFlow({
43
emailAddressId: emailAddressId,
44
redirectUrl: "https://your-app.domain.com/verification",
45
});
46
47
// Check the verification result.
48
const verification = res.firstFactorVerification;
49
if (verification.verifiedFromTheSameClient()) {
50
setVerified(true);
51
// If you're handling the verification result from
52
// another route/component, you should return here.
53
// See the <Verification/> component as an
54
// example below.
55
// If you want to complete the flow on this tab,
56
// don't return. Simply check the sign in status.
57
return;
58
} else if (verification.status === "expired") {
59
setExpired(true);
60
}
61
if (res.status === "complete") {
62
setActive({session:res.createdSessionId)
63
//Handle redirect
64
return;
65
}
66
}
67
68
if (expired) {
69
return (
70
<div>Magic link has expired</div>
71
);
72
}
73
74
if (verified) {
75
return (
76
<div>Signed in on other tab</div>
77
);
78
}
79
80
return (
81
<form onSubmit={submit}>
82
<input
83
type="email"
84
value={emailAddress}
85
onChange={e => setEmailAddress(e.target.value)}
86
/>
87
<button type="submit">
88
Sign in with magic link
89
</button>
90
</form>
91
);
92
}
93
94
// pages/verification.jsx
95
// Handle magic link verification results. This is
96
// the final step in the magic link flow.
97
function Verification() {
98
const [
99
verificationStatus,
100
setVerificationStatus,
101
] = React.useState("loading");
102
103
const { handleMagicLinkVerification } = useClerk();
104
105
React.useEffect(() => {
106
async function verify() {
107
try {
108
await handleMagicLinkVerification({
109
redirectUrl: "https://redirect-to-pending-sign-in-like-2fa",
110
redirectUrlComplete: "https://redirect-when-sign-in-complete",
111
});
112
// If we're not redirected at this point, it means
113
// that the flow has completed on another device.
114
setVerificationStatus("verified");
115
} catch (err) {
116
// Verification has failed.
117
let status = "failed";
118
if (isMagicLinkError(err) && err.code === MagicLinkErrorCode.Expired) {
119
status = "expired";
120
}
121
setVerificationStatus(status);
122
}
123
}
124
verify();
125
}, []);
126
127
if (verificationStatus === "loading") {
128
return <div>Loading...</div>
129
}
130
131
if (verificationStatus === "failed") {
132
return (
133
<div>Magic link verification failed</div>
134
);
135
}
136
137
if (verificationStatus === "expired") {
138
return (
139
<div>Magic link expired</div>
140
);
141
}
142
143
return (
144
<div>
145
Successfully signed in. Return to the original tab to continue.
146
</div>
147
);
148
}
1
import React from "react";
2
import {
3
BrowserRouter as Router,
4
Routes,
5
Route,
6
useNavigate,
7
} from "react-router-dom";
8
import {
9
ClerkProvider,
10
ClerkLoaded,
11
MagicLinkErrorCode,
12
isMagicLinkError,
13
UserButton,
14
useClerk,
15
useSignIn,
16
} from "@clerk/clerk-react";
17
18
const frontendApi = process.env.REACT_APP_CLERK_FRONTEND_API;
19
20
function App() {
21
return (
22
<Router>
23
<ClerkProvider frontendApi={frontendApi}>
24
<Routes>
25
{/* Root path shows sign in page. */}
26
<Route
27
path="/"
28
element={
29
<>
30
<SignedOut>
31
<SignInMagicLink />
32
</SignedOut>
33
<SignedIn>
34
<UserButton afterSignOutAllUrl="/" />
35
</SignedIn>
36
</>
37
}
38
/>
39
40
{/* Define a /verification route that handles magic link result */}
41
<Route
42
path="/verification"
43
element={
44
<ClerkLoaded>
45
<MagicLinkVerification />
46
</ClerkLoaded>
47
} />
48
</Routes>
49
</ClerkProvider>
50
</Router>
51
);
52
}
53
54
// Render the sign in form.
55
// Collect user's email address and send a magic link with which
56
// they can sign in.
57
function SignInMagicLink() {
58
const [emailAddress, setEmailAddress] = React.useState("");
59
const [expired, setExpired] = React.useState(false);
60
const [verified, setVerified] = React.useState(false);
61
const navigate = useNavigate();
62
const { signIn, isLoaded, setSession } = useSignIn();
63
64
if (!isLoaded) {
65
return null;
66
}
67
68
const { startMagicLinkFlow, cancelMagicLinkFlow } = signIn.createMagicLinkFlow();
69
70
async function submit(e) {
71
e.preventDefault();
72
setExpired(false);
73
setVerified(false);
74
75
// Start the sign in flow, by collecting
76
// the user's email address.
77
const si = await signIn.create({ identifier: emailAddress });
78
const { emailAddressId } = si.supportedFirstFactors.find(
79
ff => ff.strategy === "email_link" && ff.safeIdentifier === emailAddress
80
);
81
82
// Start the magic link flow.
83
// Pass your app URL that users will be navigated
84
// res will hold the updated sign in object.
85
const res = await startMagicLinkFlow({
86
emailAddressId: emailAddressId,
87
redirectUrl: "https://your-app.domain.com/verification",
88
});
89
90
// Check the verification result.
91
const verification = res.firstFactorVerification;
92
if (verification.verifiedFromTheSameClient()) {
93
setVerified(true);
94
// If you're handling the verification result from
95
// another route/component, you should return here.
96
// See the <MagicLinkVerification/> component as an
97
// example below.
98
// If you want to complete the flow on this tab,
99
// don't return. Simply check the sign in status.
100
return;
101
} else if (verification.status === "expired") {
102
setExpired(true);
103
}
104
if (res.status === "complete") {
105
// Sign in is complete, we have a session.
106
// Navigate to the after sign in URL.
107
setSession(
108
res.createdSessionId,
109
() => navigate("/after-sign-in-path"),
110
);
111
return;
112
}
113
}
114
115
if (expired) {
116
return (
117
<div>Magic link has expired</div>
118
);
119
}
120
121
if (verified) {
122
return (
123
<div>Signed in on other tab</div>
124
);
125
}
126
127
return (
128
<form onSubmit={submit}>
129
<input
130
type="email"
131
value={emailAddress}
132
onChange={e => setEmailAddress(e.target.value)}
133
/>
134
<button type="submit">
135
Sign in with magic link
136
</button>
137
</form>
138
);
139
}
140
141
// Handle magic link verification results. This is
142
// the final step in the magic link flow.
143
function MagicLinkVerification() {
144
const [
145
verificationStatus,
146
setVerificationStatus,
147
] = React.useState("loading");
148
149
const { handleMagicLinkVerification } = useClerk();
150
151
React.useEffect(() => {
152
async function verify() {
153
try {
154
await handleMagicLinkVerification({
155
redirectUrl: "https://redirect-to-pending-sign-in-like-2fa",
156
redirectUrlComplete: "https://redirect-when-sign-in-complete",
157
});
158
// If we're not redirected at this point, it means
159
// that the flow has completed on another device.
160
setVerificationStatus("verified");
161
} catch (err) {
162
// Verification has failed.
163
let status = "failed";
164
if (isMagicLinkError(err) && err.code === MagicLinkErrorCode.Expired) {
165
status = "expired";
166
}
167
setVerificationStatus(status);
168
}
169
}
170
verify();
171
}, []);
172
173
if (verificationStatus === "loading") {
174
return <div>Loading...</div>
175
}
176
177
if (verificationStatus === "failed") {
178
return (
179
<div>Magic link verification failed</div>
180
);
181
}
182
183
if (verificationStatus === "expired") {
184
return (
185
<div>Magic link expired</div>
186
);
187
}
188
189
return (
190
<div>
191
Successfully signed in. Return to the original tab to continue.
192
</div>
193
);
194
}
195
196
export default App;
1
const signIn = window.Clerk.client.signIn;
2
const {
3
startMagicLinkFlow,
4
cancelMagicLinkFlow,
5
} = signIn.createMagicLinkFlow();
6
7
const { email_address_id } = signIn.supportedFirstFactors.find(
8
ff => ff.strategy === "email_link"
9
&& ff.safe_identifier === "your-users-email"
10
);
11
12
// Pass your app URL that users will be navigated
13
// when they click the magic link from their
14
// email inbox.
15
const res = await startMagicLinkFlow({
16
emailAddressId,
17
redirectUrl: "https://redirect-from-email-magic-link",
18
});
19
if (res.status === "completed") {
20
// sign in completed
21
} else {
22
// sign in still pending
23
}
24
// Cleanup
25
cancelMagicLinkFlow();

Email address verification

Magic links can also provide a nice user experience for verifying email addresses that users add when updating their profiles. The flow is similar to one-time code verification, but users need only click on the magic link; there's no need to return to your app.

  1. Collect the user's email address.
  2. Start the magic link verification flow. There are two parts to the flow:
    1. Prepare a verification for the email address by sending an email with a magic link to the user.
    2. Wait until the magic link is clicked. This is a polling behavior that can be canceled at any time.
  3. Handle the magic link verification result accordingly. Note that the magic link can be clicked on a different device/browser than the one which initiated the flow.
    1. The verification was successful.
    2. The verification failed or the magic link has expired.

Clerk provides a highly flexible API that allows you to hook into any of the above steps while abstracting away all the complexities of a magic link-based email address verification.

1
import React from "react";
2
import { useUser, useMagicLink } from "@clerk/nextjs";
3
4
// A page where users can add a new email address.
5
function NewEmailPage() {
6
const [email, setEmail] = React.useState('');
7
const [emailAddress, setEmailAddress] = React.useState(null);
8
const [verified, setVerified] = React.useState(false);
9
10
const { user } = useUser();
11
12
async function submit(e) {
13
e.preventDefault();
14
const res = await user.createEmailAddress({ email });
15
setEmailAddress(res);
16
}
17
18
if (emailAddress && !verified) {
19
return (
20
<VerifyWithMagicLink
21
emailAddress={emailAddress}
22
onVerify={() => setVerified(true)}
23
/>
24
);
25
}
26
27
return (
28
<form onSubmit={submit}>
29
<input
30
type="email"
31
value={email}
32
onChange={e => setEmail(e.target.value)}
33
/>
34
</form>
35
);
36
}
37
38
// A page which verifies email addresses with magic links.
39
function VerifyWithMagicLink({
40
emailAddress,
41
onVerify,
42
}) {
43
const { startMagicLinkFlow } = useMagicLink(emailAddress);
44
45
React.useEffect(() => {
46
verify();
47
}, []);
48
49
async function verify() {
50
// Start the magic link flow.
51
// Pass your app URL that users will be navigated
52
// when they click the magic link from their
53
// email inbox.
54
const res = await startMagicLinkFlow({
55
redirectUrl: "https://redirect-from-email-magic-link",
56
});
57
58
// res will hold the updated EmailAddress object.
59
if (res.verification.status === "verified") {
60
onVerify();
61
} else {
62
// act accordingly
63
}
64
}
65
66
return (
67
<div>
68
Waiting for verification...
69
</div>
70
);
71
}
1
import React from "react";
2
import { useUser, useMagicLink } from "@clerk/clerk-react";
3
4
// A page where users can add a new email address.
5
function NewEmailPage() {
6
const [email, setEmail] = React.useState('');
7
const [emailAddress, setEmailAddress] = React.useState(null);
8
const [verified, setVerified] = React.useState(false);
9
10
const { user } = useUser();
11
12
async function submit(e) {
13
e.preventDefault();
14
const res = await user.createEmailAddress({ email });
15
setEmailAddress(res);
16
}
17
18
if (emailAddress && !verified) {
19
return (
20
<VerifyWithMagicLink
21
emailAddress={emailAddress}
22
onVerify={() => setVerified(true)}
23
/>
24
);
25
}
26
27
return (
28
<form onSubmit={submit}>
29
<input
30
type="email"
31
value={email}
32
onChange={e => setEmail(e.target.value)}
33
/>
34
</form>
35
);
36
}
37
38
// A page which verifies email addresses with magic links.
39
function VerifyWithMagicLink({
40
emailAddress,
41
onVerify,
42
}) {
43
const { startMagicLinkFlow } = useMagicLink(emailAddress);
44
45
React.useEffect(() => {
46
verify();
47
}, []);
48
49
async function verify() {
50
// Start the magic link flow.
51
// Pass your app URL that users will be navigated
52
// when they click the magic link from their
53
// email inbox.
54
const res = await startMagicLinkFlow({
55
redirectUrl: "https://redirect-from-email-magic-link",
56
});
57
58
// res will hold the updated EmailAddress object.
59
if (res.verification.status === "verified") {
60
onVerify();
61
} else {
62
// act accordingly
63
}
64
}
65
66
return (
67
<div>
68
Waiting for verification...
69
</div>
70
);
71
}
1
const user = window.Clerk.user;
2
const emailAddress = user.emailAddresses[0];
3
const {
4
startMagicLinkFlow,
5
cancelMagicLinkFlow,
6
} = emailAddress.createMagicLinkFlow();
7
8
// Pass your app URL that users will be navigated
9
// when they click the magic link from their
10
// email inbox.
11
const res = await startMagicLinkFlow({
12
redirectUrl: "https://redirect-from-email-magic-link",
13
});
14
if (res.verification.status === "verified") {
15
// email address was verified
16
} else {
17
// email address wasn't verified
18
}
19
// Cleanup
20
cancelMagicLinkFlow();

Was this helpful?

Clerk © 2023