Search…
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's less 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 providers "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. 1.
    User enters their email address and asks for a magic link.
  2. 2.
    Your application waits for the verification result.
  3. 3.
    Clerk sends an email to the user, containing a link to the verification URL.
  4. 4.
    User clicks the magic link. This can happen on the same device where they entered their email address, or a different device.
  5. 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. 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 Settings > User management > Authentication strategy. Simply choose Passwordless as the authentication strategy.
Enabling passwordless authentication in your instance settings.
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.
Go to your instance Settings page again, select User management > Contact Information. Make sure you select one of the following options; Email address or Email address OR phone number.
Selecting email address as the authentication identifier.
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. Now let's see how we can make some magic with our configuration.

Using Clerk Hosted Pages

If you're looking for the fastest way to implement authentication with magic links, you can leverage Clerk Hosted Pages for your sign up, sign in, and user profile pages. You can set these up on your own domain, and match your website's theme with the Clerk Dashboard to create a seamless experience.
You can find your instance's sign up and sign in links in the Home > Instance configuration section of your instance in Clerk Dashboard.
Getting the Clerk Hosted Pages URLs from the Clerk Dashboard.
By default, the URLs for your hosted pages will match the following pattern:
1
https://accounts.[your-domain].com/sign-in
2
https://accounts.[your-domain].com/sign-up
3
https://accounts.[your-domain].com/user
Copied!
For development instances, Clerk will issue you a domain on "lcl.dev". In production, you'll need to supply your own domain. See Production setup or more information
Clerk provides SDKs to make navigating to these pages easy.
Clerk React
ClerkJS
1
import {
2
RedirectToSignUp,
3
RedirectToSignIn
4
} from "@clerk/clerk-react";
5
6
// Rendering the RedirectToSignOut component will
7
// cause the browser to navigate to the Sign up
8
// URL and show the Sign Up Clerk Hosted Page.
9
function App() {
10
return <RedirectToSignUp />;
11
}
12
13
// Rendering the RedirectToSignIn component will
14
// cause the browser to navigate to the Sign in
15
// URL and show the Sign In Clerk Hosted Page.
16
function App() {
17
return <RedirectToSignIn />;
18
}
Copied!
1
// redirectToSignIn will cause the browser to
2
// visit the Clerk Hosted Pages Sign in URL.
3
window.Clerk.redirectToSignIn();
4
5
// redirectToSignIn will cause the browser to
6
// visit the Clerk Hosted Pages Sign in URL.
7
window.Clerk.redirectToSignIn();
Copied!
Read our detailed Clerk Hosted Pages guide to learn more.

Using Clerk Components

You can leverage Clerk Components in order to easily add support for magic link based authentication in your application.
Clerk provides a <SignUp /> pre-built component that renders a sign up form to handle user registrations.
Similarly, there's a <SignIn /> pre-built component that renders a sign in form and takes care of user authentication and session creation.
On the other hand for adding and verifying email addresses to a user's profile, Clerk offers a customizable <UserProfile /> pre-built component.
Note that you don't need to pass any special options to the pre-built <SignUp />, <SignIn /> and <UserProfile /> components. Magic link authentication/verification will just work, since you already configured it through the Clerk dashboard.

Sign up

Signing users up to your application is as simple as rendering the <SignUp /> component.
Clerk React
ClerkJS
1
import { SignUp } from "@clerk.clerk-react";
2
3
// SignUpPage is your custom sign up page component.
4
function SignUpPage() {
5
return (
6
// The Clerk SignUp component needs no special
7
// configuration. Passwordless authentication
8
// will just work when configured from the
9
// Clerk dashboard.
10
<SignUp />
11
);
12
}
Copied!
1
<html>
2
<body>
3
<div id="sign-up"></div>
4
5
<script>
6
const signUpEl = document.getElementById("sign-up");
7
// Mount the pre-built Clerk SignUp component
8
// in an HTMLElement on your page.
9
window.Clerk.mountSignUp(signUpEl);
10
11
// Render the SignUp component as a
12
// modal on the page.
13
window.Clerk.openSignUp();
14
</script>
15
</body>
Copied!

Sign in

Signing users in with a magic link is as simple as mounting the <SignIn /> component.
Clerk React
ClerkJS
1
import { SignIn } from "@clerk/clerk-react";
2
3
// SignInPage is your custom sign in page component.
4
function SignInPage() {
5
return (
6
// The Clerk SignIn component needs no special
7
// configuration.
8
<SignIn />
9
);
10
}
Copied!
1
<html>
2
<body>
3
<div id="sign-in"></div>
4
5
<script>
6
const signInEl = document.getElementById("sign-in");
7
// Mount the pre-built Clerk SignIn component
8
// in an HTMLElement on your page.
9
window.Clerk.mountSignIn(signInEl);
10
11
// Render the SignIn component as a
12
// modal on the page.
13
window.Clerk.openSignIn();
14
</script>
15
</body>
Copied!

Email address verification

Users can add email addresses through their profile pages and they will be verified via magic links. Simply render the <UserProfile /> component.
Clerk React
ClerkJS
1
import { UserProfile } from "@clerk/clerk-react";
2
3
// Profile is your custom user profile page component.
4
function Profile() {
5
return (
6
// The Clerk UserProfile component needs no special
7
// configuration.
8
<UserProfile />
9
);
10
}
Copied!
1
<html>
2
<body>
3
<div id="profile"></div>
4
5
<script>
6
const profileEl = document.getElementById("profile");
7
// Mount the pre-built Clerk UserProfile component
8
// in an HTMLElement on your page.
9
window.Clerk.mountUserProfile(profileEl);
10
</script>
11
</body>
Copied!
If you're interested in more pre-built offerings, you can read more about Clerk Components.

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

Registration with magic links follows a set of steps that require users to enter their email address as their 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.
  1. 1.
    Initiate the sign up process, by collecting the user's identifier. It must be their email address.
  2. 2.
    Start the magic link verification flow. There's two parts to the flow:
    1. 1.
      Prepare a verification for the email address by sending an email with a magic link to the user.
    2. 2.
      Wait until the magic link is clicked. This is a polling behavior which can be cancelled at any time.
  3. 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. 1.
      The verification was successful so you need to continue with the sign up flow.
    2. 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 sign up flow.
ClerkReact
ClerkJS
1
import React from "react";
2
import {
3
BrowserRouter as Router,
4
Switch,
5
} from "react-router-dom";
6
import {
7
MagicLinkErrorCode,
8
isMagicLinkError,
9
} from "@clerk/clerk-js";
10
import {
11
ClerkProvider,
12
UserButton,
13
useClerk,
14
useNavigate,
15
useSignUp,
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
<Switch>
25
{/* Root path shows sign up page. */}
26
<Route path="/">
27
<SignedOut>
28
<SignUpMagicLink />
29
</SignedOut>
30
<SignedIn>
31
<UserButton afterSignOutAllUrl="/" />
32
</SignedIn>
33
</Route>
34
35
{/* Define a /verification route that handles magic link result */}
36
<Route path="/verification">
37
<MagicLinkVerification />
38
</Route>
39
</Switch>
40
</ClerkProvider>
41
</Router>
42
);
43
}
44
45
// Render the sign up form.
46
// Collect user's email address and send a magic link with which
47
// they can sign up.
48
function SignUpMagicLink() {
49
const [emailAddress, setEmailAddress] = React.useState("");
50
const [expired, setExpired] = React.useState(false);
51
const [verified, setVerified] = React.useState(false);
52
const { setSession } = useClerk();
53
const { navigate } = useNavigate();
54
const signUp = useSignUp();
55
56
const {
57
startMagicLinkFlow,
58
cancelMagicLinkFlow,
59
} = React.useMemo(
60
() => signUp.createMagicLinkFlow(),
61
[signUp],
62
);
63
64
// Cleanup on component unmount.
65
useEffect(() => cancelMagicLinkFlow, []);
66
67
async function submit(e) {
68
e.preventDefault();
69
setExpired(false);
70
setVerified(false);
71
72
// Start the sign up flow, by collecting
73
// the user's email address.
74
await signUp.create({ emailAddress });
75
76
// Start the magic link flow.
77
// Pass your app URL that users will be navigated
78
// when they click the magic link from their
79
// email inbox.
80
// su will hold the updated sign up object.
81
const su = await startMagicLinkFlow({
82
callbackUrl: "https://email-magic-link-url",
83
});
84
85
if (su.status === "complete") {
86
// Sign up is complete, we have a session.
87
// Navigate to the after sign up URL.
88
setSession(
89
su.createdSessionId,
90
() => navigate("https://after-sign-up-url"),
91
);
92
return;
93
}
94
95
// Check the verification result.
96
const verification = su.verifications.emailAddress;
97
if (verification.status === "expired") {
98
setExpired(true);
99
} else if (verification.verifiedFromTheSameClient()) {
100
setVerified(true);
101
}
102
}
103
104
if (expired) {
105
return (
106
<div>Magic link has expired</div>
107
);
108
}
109
110
if (verified) {
111
return (
112
<div>Signed in on other tab</div>
113
);
114
}
115
116
return (
117
<form onSubmit={submit}>
118
<input
119
type="email"
120
value={emailAddress}
121
onChange={e => setEmailAddress(e.target.value)}
122
/>
123
<button type="submit">
124
Sign up with magic link
125
</button>
126
</form>
127
);
128
}
129
130
// Handle magic link verification results. This is
131
// the final step in the magic link flow.
132
function MagicLinkVerification() {
133
const [
134
verificationStatus,
135
setVerificationStatus,
136
] = React.useState("loading");
137
138
const { handleMagicLinkVerification } = useClerk();
139
140
React.useEffect(() => {
141
async function verify() {
142
try {
143
await handleMagicLinkVerification({
144
redirectUrl: "https://redirect-to-pending-sign-up",
145
redirectUrlComplete: "https://redirect-when-sign-up-complete",
146
});
147
// If we're not redirected at this point, it means
148
// that the flow has completed on another device.
149
setVerificationStatus("verified");
150
} catch (err) {
151
// Verification has failed.
152
let status = "failed";
153
if (isMagicLinkError(err) && err.code === MagicLinkErrorCode.Expired) {
154
status = "expired";
155
}
156
setVerificationStatus(status);
157
}
158
}
159
verify();
160
}, []);
161
162
if (verificationStatus === "loading") {
163
return <div>Loading...</div>
164
}
165
166
if (verificationStatus === "failed") {
167
return (
168
<div>Magic link verification failed</div>
169
);
170
}
171
172
if (verificationStatus === "expired") {
173
return (
174
<div>Magic link expired</div>
175
);
176
}
177
178
return (
179
<div>
180
Successfully signed up. Return to the original tab to continue.
181
</div>
182
);
183
}
184
185
export default App;
Copied!
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
redirect_url: "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();
Copied!

Sign in

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

Email address verification

Magic links can also provide a nice user experience for verifying email addresses that users add through when updating their profile. 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. 1.
    Collect the user's email address.
  2. 2.
    Start the magic link verification flow. There's two parts to the flow:
    1. 1.
      Prepare a verification for the email address by sending an email with a magic link to the user.
    2. 2.
      Wait until the magic link is clicked. This is a polling behavior which can be cancelled at any time.
  3. 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. 1.
      The verification was successful.
    2. 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.
ClerkReact
ClerkJS
1
import { useEffect, useMemo, useState } from "react";
2
import { useUser } from "@clerk/clerk-react";
3
4
// A page where users can add a new email address.
5
function NewEmailPage() {
6
const [email, setEmail] = useState('');
7
const [emailAddress, setEmailAddress] = useState(null);
8
const [verified, setVerified] = 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 {
44
startMagicLinkFlow,
45
cancelMagicLinkFlow,
46
} = useMemo(
47
() => emailAddress.createMagicLinkFlow(),
48
[emailAddress],
49
);
50
51
useEffect(() => {
52
verify();
53
// Cleanup on component unmount.
54
return cancelMagicLinkFlow;
55
}, []);
56
57
async function verify() {
58
// Start the magic link flow.
59
// Pass your app URL that users will be navigated
60
// when they click the magic link from their
61
// email inbox.
62
const res = await startMagicLinkFlow({
63
callbackUrl: "https://redirect-from-email-magic-link",
64
});
65
66
// res will hold the updated EmailAddress object.
67
if (res.verification.status === "verified") {
68
onVerify();
69
} else {
70
// act accordingly
71
}
72
}
73
74
return (
75
<div>
76
Waiting for verification...
77
</div>
78
);
79
}
Copied!
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
redirect_url: "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();
Copied!
Last modified 8d ago