Skip to main content

Implementing Magic Link Authentication On Your eCommerce Website

Let me introduce you to the idea of magic link authentication, its security risk, and its basic implementation.

Implementing Magic Link Authentication On Your eCommerce Website

There are many ways how to authenticate users. Besides standard password authentication and 3rd party login, there is also passwordless authentication known as magic link authentication.

What Is Magic Link Authentication?

Magic link authentication is a passwordless authentication with o short-lived link distributed via a secure channel. Typically a log-in page form includes just one field for an email or username. The user receives generated link or log-in code via email, SMS, or any other independent channel. Opening the magic link will successfully sign the user to the website or app.

In comparison with password authentication, there is no need for a user to create strong passwords. Also, there are no technical requirements since there is no need to store a user password. The magic link channel has to be secure to avoid someone else stealing the magic link. However, even password authentication has magic link authentication in a way. It is a similar process when a user sends a request to reset a password, usually via email.

Some popular websites that use magic link authentication are Slack, Notion, and Crystallize.

Magic Link and Security

It is impossible to say magic link authentication is more secure than standard password authentication. Not using passwords is beneficial because users tend to use not strong and not unique passwords. Regardless, technical implementation requires similar effort, even when there is no logic to store passwords securely.

There is always a security risk, and you should do solid research before designing your authentication flow. Some of these questions can help you with the flow design:

  • What can authenticated users do? Display sensitive data? Make a payment? Or only see the latest orders and favorite items?
  • Does a user have the option to revert recent actions like canceling the latest orders?
  • How long should the user stay signed in? One hour, one day, or one week?
  • The responsible person for having a secure email provider (with S/MIME or TLS at least) is the user. Ideally, email with 2FA. Is that OK for you?

You can think of security as an investment but in the opposite way. It’s up to you how much effort you invest. More knowledge and practical steps reduce the risk, but there will always be a risk. The risk can be on a different scale.

Implementation

This time, we do not expect to store sensitive data, we have an independent payment system like Klarna, and we want to give users the option to see their latest orders. All code examples are in TypeScript.

First, we need magic and a unique token. This token should be unique and short-term. In this case, we can use JWT and jsonwebtoken library:

import jwt from 'jsonwebtoken'

const MAGIC_JWT_SECRET = process.env.MAGIC_JWT_SECRET || ''

function getMagicJwt(emailAddress: string): string {
  return jwt.sign({ sub: emailAddress }, MAGIC_JWT_SECRET, { expiresIn: '1h' })
}

We will compose a magic URL with this JWT and send it to a user via email. We can use this function as REST or GraphQL endpoint implementation.

export const APP_BASE_URL = process.env.APP_BASE_URL || 'https://app.com'

async function sendMagicLink(emailAddress: string): Promise<BooleanResponse> {
  const magicJwt = getMagicJwt(emailAddress)
  const magicLink = `${APP_BASE_URL}/login?jwt=${magicJwt}`

  const emailSent = await emailService.sendMagicLinkEmail({
    magicLink,
    emailAddress,
  })

  return { success: emailSent }
}

You could notice we did not store the JWT in any database or short-term storage. In our case, we expect the app will store the JWT securely and use the JWT for authentication as the HTTP header. We will verify the JWT and get the email in a simple function.

function verifyAndParseEmail(magicJwt: string): Nullable<string> {
  try {
    const decodedValue = jwt.verify(magicJwt, MAGIC_JWT_SECRET)
    const { sub: emailAddress } = decodedValue

    if (!emailAddress) {
      return null
    }

    return emailAddress
  } catch (error) {
    return null
  }
}

We can integrate this function as authentication middleware and return the user data only in case of valid JWT.

Further Steps

This article showed a minimal setup for magic link authentication. You can eventually improve the security by:

  • Use the magic link to generate auth token and make the link invalid after the first use.
  • Add 2FA or post-login verification.
  • Improve observability to spot weird log-in attempts.

In any case, I strongly suggest not underestimating the risk and always revalidating your authentication implementation.

With authenticated user, you can connect with the customer and provide him with more features. I would like to give a few examples you can apply to your eCommerce website: 

  • Order history and management (e.g., make the same order as last time)
  • Personal newsletter preferences
  • Wishlist, favorite items, etc. 

You don’t need to store user data with the magic link, and you can forward integrated service data. Magic link is a simple solution in the end.