Magic Link vs OTP vs Password: Which Login to Use
Use an e-mail one-time code (OTP) as your default passwordless login, add a magic link in the same e-mail for people who are on the same device, and keep passwords only if your users expect them or sign in many times a day. Magic links are the smoothest when the e-mail opens on the same device, but they break when it does not, and some corporate e-mail scanners click them before the user does. Codes work across devices and inside mobile apps, at the cost of one extra step.
This article is about choosing. For how each passwordless method works under the hood, including passkeys and SMS, see passwordless authentication.
#The short answer by situation
| Your situation | Use |
|---|---|
| Consumer web app, users sign in once a week or less | E-mail code plus a magic link in the same e-mail |
| Mobile app (iOS, Android, React Native, Flutter) | E-mail code |
| B2B app sold to companies | Code first; magic links often get pre-clicked by mail security tools |
| Daily-use tool (users sign in many times a day) | Password or passkey, with a code as fallback |
| Admin panels, finance, anything sensitive | Passkey or password plus MFA, never e-mail alone |
| Users without reliable e-mail | Phone and SMS code |
#Magic link vs OTP: what is actually different
Both send a secret to the user's inbox. Both prove the same thing: the person controls that e-mail address. The difference is where the secret is used.
| Magic link | E-mail OTP (code) | |
|---|---|---|
| User action | Click a link | Type 6 digits |
| Works when e-mail is read on another device | No, the other device gets signed in | Yes |
| Works inside a native mobile app | Needs a deep link set up | Yes, no setup |
| Affected by e-mail security scanners | Yes, a scanner can open the link first | No |
| Phishing risk | Low (nothing to type into a fake site) | Higher (a fake site can ask for the code) |
| Time to sign in | Fastest on the same device | A few seconds more |
| Brute force | Not practical (long token) | Needs attempt limits (short code) |
#The cross-device problem
A user starts sign-in on a laptop, but reads e-mail on a phone. They tap the magic link. The phone browser is now signed in, and the laptop is still on the "check your inbox" screen. With a code, they read 6 digits off the phone and type them on the laptop. This single case is why most teams that start with magic links later add codes.
#The scanner problem
Many company mail systems open every link in incoming mail to check it for malware. If your magic link is single-use, the scanner uses it, and the user gets "link expired". Codes are not affected, because there is nothing to click.
#The phishing trade-off
Codes have one real weakness: a fake sign-in page can ask the user for their code and pass it on in real time. Magic links are harder to phish this way, since nothing is typed. Neither method stops a determined attacker the way a passkey does. If phishing is your main threat, use passkeys.
#Magic link vs password
Passwords are still the right choice in a few cases:
- High-frequency use. If people sign in several times a day, a password manager autofill beats opening e-mail every time.
- Offline or e-mail-poor environments. Shared devices, warehouses, kiosks.
- User expectation. Some audiences distrust a login with no password field.
Passwords also bring the costs everyone knows: reuse across sites, credential stuffing, reset flows and support tickets. If you keep them, follow current guidance. NIST SP 800-63B (revision 4) says passwords used as the only factor must be at least 15 characters, that services shall not impose composition rules (like "one symbol"), shall not force periodic changes, and shall check new passwords against a blocklist of known-bad passwords (NIST SP 800-63B).
Magic links remove all of that, but they move the security of your app to the security of the user's inbox. For most consumer apps that is a good trade. For sensitive apps, add a second factor either way.
#Magic link vs e-mail OTP: the best of both
You do not have to choose. Put both in one e-mail:
Your sign-in code is 482913. Or sign in with one click: [link]
Users on the same device click. Users on another device, inside a mobile app, or behind a scanner type the code. On StitchDB this is one template change. In Studio, open Authentication › Emails, edit the Magic link template and include both variables:
<p>Your sign-in code is <strong>{{ .Token }}</strong>.</p>
<p>Or <a href="{{ .ConfirmationURL }}">sign in with one click</a>.</p>Then one call sends the e-mail and either path works:
import { createClient } from 'stitchdb-js'
const stitchdb = createClient('https://<project-ref>.p.stitchdb.com', anonKey)
// 1. Send the e-mail (link + code)
await stitchdb.auth.signInWithOtp({
email: '[email protected]',
options: { emailRedirectTo: 'https://example.com/welcome', shouldCreateUser: true },
})
// 2a. The user clicked the link: the SDK picks the session up from the URL.
// 2b. The user typed the code:
const { data, error } = await stitchdb.auth.verifyOtp({
email: '[email protected]',
token: '482913',
type: 'email',
})The link redirects to emailRedirectTo, which must be on your redirect URLs list. Codes have 6 digits by default (up to 10) and expire after one hour; you can change both under Authentication › Sign-in methods (passwordless docs). With shouldCreateUser: false, only existing accounts get an e-mail, which is what you want for invite-only apps.
In Swift the same flow is two calls:
try await stitchdb.auth.signInWithOTP(email: "[email protected]", redirectTo: URL(string: "myapp://login-callback"))
try await stitchdb.auth.verifyOTP(email: "[email protected]", token: "482913", type: .email)#Things that go wrong in production
Slow or missing e-mails. Every passwordless login depends on delivery. Send from a domain you control with SPF and DKIM set up. On StitchDB paid plans you can send auth e-mails from your own domain without an SMTP server, or plug in your own SMTP provider (e-mail docs).
Code expiry too long. One hour is a safe default for links. For codes, shorter is better: 10 to 15 minutes covers nearly everyone.
No attempt limits. A 6-digit code has a million combinations. Without limits, a script can guess it. StitchDB limits verification attempts and magic link requests to 30 per 5 minutes per IP by default, adjustable in Authentication › Settings (auth limits). If you need stricter limits on one endpoint, add an API rate limit rule for the Auth verify path.
Bots using your login form to send e-mail. A public "send me a link" form is an e-mail cannon. Add a CAPTCHA (bot protection); StitchDB also refuses throwaway addresses and mass sign-ups by default.
Account enumeration. Do not reply "no account with that e-mail". Show the same "check your inbox" message either way.
Redirect to the wrong place. Set the Site URL to production before launch; otherwise links in e-mails point at localhost:3000.
#How to decide in one minute
- Is this an admin, finance or health app? Use passkeys or password plus MFA.
- Is it mostly mobile? Use e-mail codes.
- Do users sign in many times a day? Offer passwords (or passkeys), with a code as fallback.
- Otherwise: send an e-mail with both a code and a link.
You can offer more than one. Users who signed up with a code and later add a password or Google keep one account, because identities with the same verified e-mail are linked automatically.
#FAQ
#Is a magic link more secure than an OTP?
Slightly, against phishing, because there is nothing to type into a fake page. Codes are more reliable across devices and against e-mail scanners. Both are only as secure as the user's inbox.
#Is a magic link more secure than a password?
Usually yes for typical users, because it removes password reuse and credential stuffing. A strong unique password plus MFA is stronger than either alone.
#How long should a login code be valid?
10 to 15 minutes is a common choice. Long enough for slow e-mail, short enough to limit guessing. Combine it with attempt limits.
#Do magic links work in a mobile app?
Yes, with a deep link (for example myapp://login-callback) added to your redirect URLs. Codes are simpler on mobile because they need no deep link at all.
#Can I use both magic links and passwords?
Yes. Many apps offer a password field and a "send me a code" button. Accounts with the same verified e-mail are linked to one user.
Set up codes and links in a few minutes: read passwordless sign-in and create a free project.