Contact salesSign inSign up

Authsignal secures millions of passkey transactions out of our hosted Sydney region.

Authsignal secures millions of passkey transactions out of our hosted Sydney region.

Join us today!
Blog
/
Current article

How to implement passkeys for a seamless E-commerce checkout experience.

Published:
October 1, 2024
Last Updated:
October 9, 2024
Steven Clouston
How to Implement Passkeys for a Seamless E-Commerce Checkout Experience.

Implementing passkeys at checkout on your eCommerce store enables you to deliver a seamless and secure experience for your customers. Passkeys enable a new standard of authentication not seen before in the eCommerce marketplace. Brands like Shopify are now starting to set a new standard in this space with their streamlined checkout processes, combining simplicity with strong authentication methods employing passkeys.

Below, we dive into how to integrate passkeys at checkout for your customers.

In this guide, we’ll walk through how to use Authsignal’s email OTP authenticator and passkeys to create a seamless user experience for your e-commerce platform. We'll focus on the technical aspects of implementing these features, giving you a clear, actionable path to building a secure passwordless authentication flow.

Step-by-Step Guide to Implementing Authsignal’s Email OTP and Passkeys

For brevity, this implementation guide focuses on the specific requirements for integrating Authsignal. Some more general steps, such as frontend setup and common implementation details, have been simplified to keep the focus on the core Authsignal integration.

This guide uses Authsignal's Web and Node SDKs. Authsignal provides SDKs for a number of languages, so you can adapt this guide to suit your needs:

Step 1: Set Up Email OTP Authentication

Email OTP (One-Time Password) is the first step in our passwordless authentication process. It provides a simple yet secure method for users to verify their identity during sign-up or login.

This initiates the authentication chain, with email OTP as the primary security layer. Once verified, users can add additional authenticators like passkeys. Email OTP also serves as a reliable backup when passkeys are unavailable, creating a robust, multi-layered authentication system.

Our process involves checking if a user is already verified. For verified users with an email authenticator, we'll prompt them to complete an email OTP challenge and pre-fill their information. New users will be asked to complete their details. After verification, we proceed with the checkout.

To begin, sign in to Authsignal's authenticators section and set up an email OTP authenticator.

1. Backend: Create an endpoint that checks if the user has a verified account. If so, this will return a URL that the frontend can use to launch Authsignal’s pre-built UI. We will call this endpoint /lookup-user but you can call it what you like.

//POST /lookup-user

//find the user in your database by looking up their email

const authsignalUser = await authsignal.getUser({ userId: user.id });

if (authsignalUser?.enrolledVerificationMethods?.includes(EMAIL_OTP)) {
  // The user has an email authenticator
  
  const response = await authsignal.track({
    email: req.body.email,
    userId: user.id,
    action: "sign-in",
    redirectUrl: `${baseUrl}/activity`,
    deviceId: req.body.deviceId,
    userAgent: req.headers["user-agent"],
    ipAddress: requestIp.getClientIp(req) ?? undefined,
  });

  res.status(201).json(response.url);
} else {
  // The user has not verified using email - you could throw an error here
  // and handle it on the frontend
}

For more detailed information about getting the Authsignal user, refer to the documentation.

2. Backend: Create an endpoint called /sign-in that we’ll use to generate a session cookie on successful authentication. This endpoint needs to accept a token that we'll receive from Authsignal. The session token generated is independent of Authsignal and is created using a library of your choice (iron-session for example).

//POST sign-in

const { isValid, userId } = await authsignal.validateChallenge({
  token: req.body.token,
});

if (!isValid || !userId) {
  res.status(401).send("User not found");
  return;
}

//find the user in your database using the userId

//if the user is found, generate a session token 
//(you could use something like iron-session for this)

//set a cookie on the frontend that contains the session token 

3. Backend: Create an endpoint called /enroll. This endpoint will create a user in the database and track an action that we can later validate using the /sign-in endpoint.

//POST enroll

//create a user in the database

const response = await authsignal.track({
  email: req.body.email,
  userId: user.id,
  action: "enroll",
  deviceId: req.body.deviceId,
  userAgent: req.headers["user-agent"],
  ipAddress: requestIp.getClientIp(req) ?? undefined,
});

res.status(201).json(response.url);
return;

4. Backend: Create an endpoint called /checkout. This authenticated endpoint will handle product purchases. It uses the session token generated in the /sign-in endpoint—don't confuse this with an Authsignal token.

 //POST checkout
 
 const token = req.cookies.token;
 
 //validate the session token and extract the user ID
 
 //run your checkout logic

5. Frontend:  Call the /lookup-user endpoint we created earlier to check if the user has a verified account. This will lead to either scenario A or B, which we'll explore in detail below.

Scenario A: The user does not already have a verified account:

A.1 Frontend: Display a form to collect the user’s details.

A.2 Frontend: Upon form submission, call the /enroll endpoint to create the user in your database and track an enroll action. Use the URL returned from /enroll to launch Authsignal's pre-built UI. The user will only be considered verified after successfully authenticating via email OTP.

const { token: authsignalToken } = await authsignal.launch(url, {
    mode: "popup",
});

Upon successful verification, Authsignal provides a token that we can use to verify the user on the server side and generate a session token.

Our sign-in endpoint will handle this step - we’ll pass it the authsignalToken returned from Authsignal in the code above. For simplicity in this demo, we'll also store the authsignalToken in local storage. This temporary authsignalToken enables the user to add an additional authenticator (passkey in this case) later if needed.

//the sign-in endpoint will set the session cookie if successful 
await fetch(`${baseUrl}/sign-in`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ token: authsignalToken }),
    credentials: "include",
  });


 localStorage.setItem("authsignal_token", authsignalToken);

Now that the user is authenticated, we can call the authenticated /checkout endpoint, and you can run your checkout logic.

Admin portal setup

To ensure that the user can only enroll using an email OTP authenticator, navigate to Authsignal’s actions page to configure the enroll action you have tracked.

We'll configure the enroll action to accept only the email OTP authenticator, as shown below. This is crucial because we want email OTP to be the first authenticator added to the authentication chain. Starting with a passkey as the first authenticator would be problematic since passkeys require a fallback authentication mechanism—which email OTP provides. Learn more about Authsignal's authenticators here.

Scenario B: The user already has a verified account:

For users with verified accounts, we'll use Authsignal's email OTP to sign them in and generate a session token. This session token allows us to fetch the user's details and populate the checkout form.

B.1 Frontend: Launch Authsignal’s pre-built UI in popup mode using the URL returned from the /lookup-user endpoint.

const { token: authsignalToken } = await authsignal.launch(url, {
  mode: "popup",
});

Upon successful verification, we will receive a token from Authsignal that can be used to verify the user on the server side and generate a session token.

Call the sign-in endpoint, passing the token received from the Authsignal launch command. For simplicity in this demo, we will also save the token to local storage, allowing us to temporarily use it for the user to add another authenticator later if needed.

await fetch(`${baseUrl}/sign-in`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ token: authsignalToken }),
  credentials: "include",
});

localStorage.setItem("authsignal_token", authsignalToken);

B.2 Frontend: With the session token now in place (distinct from Authsignal's token), we can call an authenticated endpoint to retrieve the user's information and pre-populate the form. Here's an example:

 const userDetails = await fetch(`${baseUrl}/user`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
      },
      credentials: "include",
    }); 
    
  //populate the form with the user's details

B3. Frontend: When the user clicks 'Pay now', call the/checkout endpoint to execute the required checkout logic.

At this point, you may want to redirect the user to a dashboard where they can view their purchase details.

Step 2: Add a Passkey Authenticator

We'll now add functionality to allow users to create a passkey and use it for sign-in with passkey autofill. This enhances the user experience significantly and can help convert visitors into paying customers.

Navigate to Authsignal’s authenticators page and add passkey as an authentication option.

As we are developing locally on port 5173, these are the passkey settings we’ll use. You can learn more about developing with passkeys in a local environment here.

1. Passkey prompt and passkey creation

On the first page that the user lands on after authentication, we can use Authsignal's isAvailableOnDevice check to determine if the user has a passkey on the current device. If not, we will show a prompt so they can add a passkey.

To create a passkey, the Authsignal SDK needs an authenticated user token. For simplicity in this demo, we'll use the token from the successful login via the pre-built UI, which we saved in local storage earlier. Keep in mind that this token is only valid for 10 minutes. If needed, you can generate a new token from your backend. For more details on creating passkeys, check out the documentation.

//Code to prompt the user to create a passkey on their device
async function promptToCreatePasskey() {
  
  //get the authsignal_token that we stored earlier
  const token = localStorage.getItem("authsignal_token");

  localStorage.removeItem("authsignal_token");

  // True if a passkey has already been created on this device using the Web SDK
  const isPasskeyAvailable = await authsignal.passkey.isAvailableOnDevice();

  if (token && !isPasskeyAvailable) {
    const result = await authsignal.passkey.signUp({ token });

    if (result) {
      alert("Passkey created!");
    }
  }
}

useEffect(() => {
  promptToCreatePasskey();
}, []);

2. Sign-in with a passkey using autofill

We will now make some changes to our checkout form so that passkeys are detected and shown as an option if they are available.

To enable passkey autofill, add the attribute autoComplete="username webauthn" to the email input field, as shown below. For more information about passkey autofill, check out our detailed guide.

<input
	autoComplete="username webauthn"
	...
/>

We'll also need to add the following logic that runs on page load. Upon success, we receive a token from Authsignal that we'll validate on our backend. We can pass this token to our existing /sign-in endpoint that we created earlier. If the token is valid, the sign-in endpoint will generate a session cookie for us, which we can use to retrieve the user's details.

useEffect(() => {
    const initPasskeyAutofill = async () => {
      try {
        const { token, userDisplayName: email } = await authsignal.passkey.signIn({
          action: "passkey-sign-in",
          autofill: true,
        }) || {};
  
        if (token && email) {
          await fetch(`${baseUrl}/sign-in`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({ token }),
            credentials: "include",
          });
        }
      } catch (error) {
        console.error("Error during passkey autofill:", error);
      }
    };
  
    initPasskeyAutofill();
  }, []);

Now that the user is signed-in, we can access authenticated endpoints, retrieve the user’s details and populate the form, in the same way we did with email-OTP.

The authenticated /checkout endpoint can be called when the ‘Pay now’ button is clicked.

Conclusion

Implementing a secure and user-friendly checkout process like Shopify's is now within reach for e-commerce platforms of all sizes. By leveraging Authsignal's email OTP and passkey authentication, you can create a seamless, passwordless experience that enhances both security and convenience for your customers.

This guide has walked you through the key steps:

  • Setting up email OTP authentication
  • Implementing passkey creation and autofill functionality
  • Integrating these features into your checkout flow

By following these steps, you'll be able to offer a modern, friction-free checkout experience that can help increase conversions and customer satisfaction. Remember, the key is to balance security with usability, and the combination of email OTP and passkeys achieves just that.

Try out our passkey demo
Passkey Demo
Subscribe to our monthly newsletter
Subscribe
You might also like
Passkey Recovery & Fallback: Can Passkeys Stand Alone and Fully Replace Passwords & MFA?
Passkeys simplify authentication and resist phishing, but can they truly replace passwords and MFA? Explores passkey fallback opinions, key challenges, and best practices, highlighting why passkeys are the future of authentication.
Passwordless React UI Components: Add Passkeys to Your Client-Side App
Add authentication flows into your react app or website using Authsignal’s UI components with the React SDK. Fast-track passkeys and MFA implementation for your client-side app.
Synced vs Device-Bound Passkeys: How User Convenience and Authentication Experiences Vary.
Not all passkeys are the same. Synced and device-bound passkeys offer distinct benefits and trade-offs in security, access, and user experience. This guide covers the differences and key considerations for passkey recovery.
Secure your customers’ accounts today with Authsignal.