Green button with a sticker saying push to reset the world

How to implement password reset with headless BigCommerce – Part 1: Solution overview

October 6, 2021

BigCommerce does a lot of things right in the headless space, but not everything.

Unfortunately, password reset with BigCommerce doesn't work out of the box for headless. So I had to work out how to implement a secure password reset feature, leveraging the BigCommerce platform as much as possible.

In this article series, I'll show you how you can implement the same feature.

There's a lot to this solution and the thought process behind it. As a result, this has been broken up into multiple parts. This part goes into the background and solution overview. If you're just here for the code, check out the next part of the series here.

Why can't I use the built-in password reset feature?

Of course, BigCommerce has a built-in password reset feature. How else would you manage customer accounts in their storefront? The problem is, it wasn't built for headless, not even as an afterthought. It's not strange; headless wasn't a thing when they made this feature. But it does present a problem. It's possible to trigger the password reset email via the API, but there are three issues with that email template:

  1. The token sent out won't work with headless.
  2. You can't trigger transactional emails directly via the API, which means you don't control the variables.
  3. Strange API defaults trigger the email when it shouldn't.

I'll go into more detail about these issues and what they mean later on. But for now, those are the three main problems with the built-in password reset feature from a headless perspective.

Let's take a quick look at some of the questions I asked myself and BigCommerce when I rolled my own headless password reset feature.

Could I use the redirects and pass the token to my headless site?

Yeah, you could, but you can't verify the token. Remember, it's secure and signed for a very good reason.

What security implications do I have to keep in mind if I build a password reset feature?

Password reset is a critical functionality. It is not something you want to get wrong and expose a way for malicious attackers to gain access to your customers' accounts. The damage that could do to your or your client's brand could be irreparable.

Firstly, you need to verify that the customer is the owner of the email. You do that by sending out a password reset email. It sounds simple enough.

But you need to verify that the link they clicked came from that email. You can't just send the email as a URL param; anyone could do that. No, you need to encode all the essential information in a token that only your servers can decode.

Further, the token has to have a limited lifetime and should only be possible to use once to prevent replay attacks where the attacker gains access to the URL with the reset token and tries to repeat the request.

Requirements

So that leads to the following requirements:

  • Trigger an email when the customer wants to reset their password.
  • The email includes a link to the password reset page.
  • That link contains a token with all essential information.
  • Only your server can decode the token.
  • The token has a short lifetime.
  • The token can only be used once.

Solution

Ok, you're getting somewhere now: you've set the requirements, you've thought about the security of the token, and you're ready to start digging in. Almost ready.

First, let's come up with a solution. There are a few questions hidden in those requirements. Those are:

  1. How do you encode and decode the token?
  2. How do you limit the token lifetime?
  3. How do you send the email?
  4. How do you ensure the token can only be used once?

Let's take a look at the details.

How do you encode and decode the token?

Encoding and decoding tokens is a solved problem. Whichever language and web platform you use, someone has already solved this issue. You don't want to reinvent the wheel on an essential security feature like this. I used the jsonwebtoken package from npm. I'm sure you'll know of an equivalent in your language.

How do you limit the token lifetime?

Limiting the lifetime of a token is built into the jsonwebtoken library. That was easy.

How do you send the email?

Well, BigCommerce has a password reset transactional email template. So maybe you can use that? But, as it turns out, no, you can't. As far as I can tell, there's no other way to trigger that email than to make a PUT request to the Customers v3 endpoint to update the customer and set the flag authentication.force_password_reset to true. Or, as you'll see in the next post, just set a new_password since a strange default seems to trigger that password reset email as long as the authentication field is present.

The email sent out by the endpoint mentioned above will point to the domain you host the checkout on (commonly subdomain.yourdomain.com) and contain a token that only BigCommerce's servers can decode, making it rather useless to you.

You will have to trigger that email from some other service. I ended up using SendGrid; they have reasonable pricing, and it's pretty straightforward to set up templates and send emails using their platform.

How do you ensure the token can only be used once?

You need to create a one time token (OTT) that you store somewhere and delete after use. The token doesn't need to contain anything sensitive; it just needs to be unique. For example, I used a SHA256 hash of the email and the current time.

You should generate that token when you send the password reset email. Then delete it once the customer resets their password. It's a one time token, so it should be generated when it's needed. Otherwise, it shouldn't be present.

You can store the token in a customer attribute. There's some setup required for that, as you'll see in the next post. But it works a treat and saves you from keeping a separate database with OTT token records.

Next steps

Now that we have a solution planned out, it's time to start coding. In the next post, I'll show you how I solved this problem with Next.js. It should be reasonably translatable to any js framework.

Read it here.