Magic links are the password-less future we can have today

Authentication on the web is the act of proving to a web app that you are who you say are. Every form of authentication boils down to one ingredient: some piece of shared secret data that both you and the web app know.

Typically, this “shared secret” is a just single password that you come up with when you create your account. To login, you prove who you are by re-typing this password every time.

“Magic links” are a different approach. Instead of a single, long-lived password, the web app generates a new, unique secret code every time you want to login. The web app then sends you an email or an SMS with a link to login. This link contains this unique secret code: your short-lived shared secret.

A magic link can look something like this:

https://bookface.com/login?code=2u7xzxmRdtSvFoyrO5al

This idea is not new. In fact, some of the most popular apps, like Slack and Medium, use magic links for logging in.

Medium Magic Link

So what should I use for my web app’s authentication layer?

In this post we’ll look at the tradeoffs between using magic links and passwords. We’ll analyze both methods on three factors: security, code complexity, and user experience.

Here's the bottom line: unless you are a siloed platform on which others build their authentication on top of (read: Apple, Google, Microsoft, etc) then choosing magic link auth for your web app means a more secure, un-phishable, password-less authentication method that is easier for you to implement and a more pleasant experience for your users.

Security

The security of any authentication scheme means making sure that the “shared secret” is actually kept a secret from everyone but you and the server. There is a lot of literature on why passwords rate very badly in the security metric, and to keep this post short let’s just look at two primary vulnerabilities:

Passwords

  • Guessable. Either you reuse the same password on multiple sites, or create simple passwords because our brains are not good at storing lots of entropy.

  • Phishable. What’s to prevent you from accidentally entering your password into a phishing site -- how are you expected to catch that trelIo != trello?

Comparatively, magic links do much better:

  • Not Guessable. The login code is computer-generated randomly for every login and usually set to expire within a short period of time. As long as the entropy used is sufficient and the time period is small, say at least 16 bytes = 128 bits = 2^128 possible codes for a 24-hour expiration.

  • Not Phishable. While WebAuthn/FIDO2/U2F is gaining traction as it can prevent phishing, magic links are already phishing proof and don't add complexity to your app! Since the login link is delivered to your inbox, if you click on that link -- it will only log you in. If an adversary tries to login as you -- you will still get the login link and only you’d be able to login with it. For added security, we can bind the browser session to the login code to prevent vulnerabilities like accidentally forwarding a magic link email.

It’s also important to consider the security of any dependent systems for each authentication mechanism. Passwords, in the simplest form, are independent -- there is no third-party company or service whose security you depend on. However, there’s a different story with magic links:

  • Trusted 3rd-Party. For example: GMail for email. If you are Google this becomes a cyclic dependency problem, in which case magic links might be a deal breaker as the primary authentication method.

  • Email provider authentication. In order to access that magic link you need to login to your inbox. Therefore, magic link auth is only as secure as your email provider login. Typically this is not a realistic concern:

    • Password reset links already depend on an email link so you already have this problem anyways with passwords

    • Many, if not most, people use Google or Apple mail and their security is provably excellent. If you’re not one of these giants, then it's better to stand on their shoulders.

    • Many users are perpetually logged into their mail provider, removing the mail login step entirely.

Summary

Magic Links Passwords Passwords + SMS/Email 2FA
Trusted 3rd Party Guessable Trusted 3rd Party
Phishable Phishable

Assuming the mail service is not compromised and the user is already logged in, magic links clearly win as they are leak-proof and phishing-proof.

Consider that you will surely need to add some form of 2FA to a password system. Without a complex U2F/FIDO2/WebAuthn implementation your users will be susceptible to phishing.

Code Complexity

Some might argue that developer implementation complexity is a facet of security: the harder something is to make, the more likely you will introduce security bugs and other errors. Another concern for you and your web app might just be development time. Magic links save a lot of time by drastically simplifying the number of lines of codes and the raw concepts you’ll need to grasp.

Even giant tech companies fumbled password authentication implementations. It's not straightforward.

Passwords
No plain-text storage. Must choose a password-based cryptographic hashing algorithm and store the hash of the password.
Salting. Hashes alone are not enough if your database is leaked. You must also salt each password.
Longterm Storage. Hash, Salt, and store both the hash and the salt in a persistant database storage.
Two-Factor. Passwords are typically weak, so you might need to consider adding Two-Factor Authentication (2FA). Will it be SMS codes/TOTP (Google Authenticator)/U2F or WebAuthn?

One way to avoid passwords is to use a single sign-on (SSO) identity service provider (IdP). If you need to have access to specific app data from an Idp (i.e. TravisCI needs to read repositories on GitHub) then this path is optimal. However, if you don’t need app data, you are just adding complexity to your app and user experience.

Single sign-on or OAuth (Google/GitHub/etc...)
OAuth callback URLs
Shared secrets and keys
Multiple auth flows on different devices
Provider lock-in
What if a user doesn't have an account?
Minimizing app data permissions

In comparision, magic link auth requires just few steps.

3-step Magic Link Authentication
1. Generate a random token
2. Store it in a secure cookie or Redis
3. Email a link to the user with the token as a query parameter.

To see how easy it is to implement a magic link solution, let’s take a look at a snippet of Go code.

func CreateAndSendMagicLink(emailAddress string) {
    token := GenerateRandomToken(32) 
    expiresIn := time.Hour
    redis.Set(token, emailAddress, expiresIn)
    
    magicLink := fmt.Sprintf(
        "https://bookface.com/login?code=%v", token
    )

    EmailMagicLink(magicLink, emailAddress)
} 

func EmailMagicLink(magicLink string, emailAddress string) {
    client := approveapi.CreateClient("sk_test_yourapikeyhere")

    prompt := approveapi.CreatePromptRequest {
        User: emailAddress,
        Body: "Click the link below to sign into Bookface.com",
        ApproveText: "Login",
        ApproveRedirectUrl: magicLink,    
    }

    _, _, err := client.ApproveApi.CreatePrompt(prompt)
}

To authenticate, when the user clicks the login link it's just a simple token lookup.

func AuthenticateToken(token string) {
    emailAddress, err := redis.Get(token)
    if err != nil {
        return err
    }

    // Done! The user with username emailAddress is now authenticated!
    LoginUser(emailAddress)
}

You can take a look at the full example on GitHub.

ApproveAPI lets you send magic links and secondary verifications to users in real-time across email, SMS, Slack, and mobile push in just one API call. Grab one of the client libraries for Python, Ruby, Rust, Java, Go, Haskell or Node.js and start sending magic links in just a few lines of code.

That’s it -- and there are plenty of libraries out there that will do this for you.

User Experience

Authentication is a necessary evil, but an implementation with bad or difficult UX will most certainly hurt conversion rates and ultimately annoy your users. Let’s look at the UX pain points with magic links and passwords for your users.

Passwords SSO/OAuth Magic Link
Easy to forget User needs an account already Switch tabs/window to magic login link email
Clunky password-managers and occasional manual copy-paste Read data permission authorization
2FA is slow with 6-digit codes Write permission authorization ("won't post on your behalf")

Ultimately, magic links are one click and done. Most users have their email open or get push notifications on their phone. It’s as easy as a single tap or click to login.

Magic links are passwordless, simple, and secure

Unless you cannot rely on another identity provider or need to access and modify the user’s account data on another Social/IdP/OAuth provider (Google, GitHub, Twitter, etc), then magic links might be the right authentication solution for you.

Magic links are password-less, un-phishable, and secure against brute-force attacks. A magic link is much easier for a user to grok and faster to use as compared to the clunk that is passwords, 2FA, and OAuth. Instead of a week long project, it will take you just a few hours to implement end to end.

While the recently W3C-approved WebAuthn protocol brings a future hope of passwordless authentication to the big platforms, most web apps can actually live in that passwordless future today with magic links.