Crystallize logo

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.

clock
Updated August 25, 2022Published August 16, 2022
clock4 minutes
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.

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.

Sign up for Crystallize newsletter!

The only dev and business oriented newsletter in the headless space!

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.