1

I am currently having an issue in which users sign up with a different email than that with which they checked out (their data gets stored in our backend by email address). However, I'd rather not block signups outside of checkout, because (as of now) I also allow signups before purchasing the product. I could automatically generate an account for users on checkout, but that wouldn't change anything if they then signed up with another email later.

My current thought is that I'll add ways to try to ensure they sign up with their email from checkout. To that end, I've just added a feature that saves their email in localStorage on checkout and will autofill on the combined login/signup page. Obviously, though, that will only help if they use the same device. My other thought was that I'd include a token on each email as a parameter on outgoing links to our portal, that would also autofill their email on login/signup for other devices.

My primary question is whether the above italicized sentence is bad security practice. I do use NextJS (along with React) so some of it is rendered server-side, but I'm thinking I'd have to handle that query parameter on the client side(?), in which case I'd probably have to create an unauthenticated endpoint for converting tokens to emails. Thus, anyone who intercepts the URL (with enough motivation) can also figure out my customers' emails. Is this a reasonable risk? Am I being unnecessarily paranoid? If not, what's an alternative way to achieve the desired result I've described? I'm open to ideas.

Would also welcome comments about the aforementioned practice of saving plaintext customer emails in localStorage.

Additional context: I use Firebase for authentication, everything is written in Typescript.

Max
  • 23
  • 3
  • [this](https://stackoverflow.com/q/643355/4513452) is a similar question, but it's asking about a token that would effectively act as the entire login, whereas I'm asking about email alone – Max Aug 09 '23 at 10:04

2 Answers2

2

First of all, if anything touches the client, consider it as compromised until proven otherwise. You cannot rely on client side code, ever. If you do, you must revalidate server side.

Let's start simple with a plain text email in the link. There are basically two risks:

  1. The email leaks and your customer gets spam
  2. Users could change the email in the link you sent with some other email

But risk is a factor of impact and likelihood. At the Internet scale, you can consider likelihood as "almost certain", even for low value assets.

The impact of these events happening will guide the controls you put in place to prevent it. If it is just a display issue, you might not have to do anything. Saying "Welcome (wrong email)" might not be worth the trouble of fixing.

But if you rely on this value to do some form of authentication, then you must ensure:

  • It cannot be forged
  • It cannot be swapped with another value
  • It cannot be reused forever

You will probably also need to think about these requirements:

  • Customer privacy is guaranteed
  • It scales

This could be a whole project, but replacing the email with a "token" will work:

  • Generate a string like timestamp+email and maybe some context, like an account number or something.
    • For example 202308091543Z_user@gmail.com_123456
    • A timestamp in minutes or hours is good enough. Make it UTC
    • Use the current time, not the expiry time for the timestamp so you can change expiry tolerance after they are sent or bulk invalide tokens older than X if things go really wrong
  • Encrypt it (a tricky part, more on this later)
  • Base64 the result is the token you send

When the server (remember, the client cannot be trusted) receives the token it will:

  1. Decrypt the token. If decryption fails, reject (see notes below)
  2. Parse it. If you cannot get a timestamp+email (and context) back, reject
  3. If the timestamp is older than N days, reject. 30 to 120 days seems reasonable, but make this configurable
  4. Bonus points for sanity checks
    • timestamp no more than minutes in the future
    • context matches database record for that email.
  5. If you get here, the token can be considered valid

A couple of notes about key management:

  • Support key rotation. Decrypt with the current key, if it fails try with the previous key if you have it. If it fails again, reject
  • I would suggest the AES-SIV or AES-GCM-SIV algorithm and mode with a 128 bits key. Use 256 bits if it saves you an argument and a meeting.
  • Every host will need access to the same keys (every versions)
  • You need to make sure the new key is available before it can be used, so a scheme that has current, future and previous keys will ease deployment
  • Try decryption with the future key to your algorithm, and use it as a signal that it is now the current key
  • Delete the previous key after N days (or after the future key becomes current), because you will reject those token regardless.
ixe013
  • 9,559
  • 3
  • 46
  • 77
  • 1
    I'd rather not shoulder the additional responsibility of building out additional infra to support such links for auth (and fwiw, firebase supports them already), though I sincerely appreciate the lesson in how I'd go about doing so. I'll still require people to use a password (and verify their email via Firebase), I just want to nudge them towards using the correct email on signup. I suppose, just for safety, I could implement something along the lines of what you mentioned for email autofill (though with a lower risk), and to ensure scalability, I can do so for only as yet unregistered users. – Max Aug 11 '23 at 07:30
1

What this reminds me of is the unsubscribe link at the bottom of mailing list emails. Those have a token as a query parameter, that leads to a page where you can configure email notification settings without having to log in. Sometimes the page itself will say something along the lines of

modifying email settings for your-email@domain.com

and to my understanding this seems to be the same as what you are thinking. If so many large companies do this with no issue, perhaps it might be ok for you to do as well.

My second thought is if a malicious actor intercepts this URL, wouldn't they already know or be able to know the customer's email, since the URL is contained inside the email?

teddyzhng
  • 80
  • 7
  • 2
    Sometimes a token is the only thing you have for authentication. What if you send an email that will allow a password reset link? That link has the same value as the password itself. That`s when the timestamp comes handy. A password reset token would expire after 24 hours, maybe even less. +I never said tokens where not secure. A scheme like I propose is probably overkill for an unsubscribe link, but its the bare minimum for a password reset link. Still, both have a token embeeded in a link. Security is not a thing, it's a journey towards a tradeoff. – ixe013 Aug 10 '23 at 02:39
  • 1
    @ixe013 I agree, thank you for the clarification to my answer – teddyzhng Aug 10 '23 at 15:06