Is it bad practice to include tokenized email address as a URL parameter on customer communication?

huangapple go评论76阅读模式
英文:

Is it bad practice to include tokenized email address as a URL parameter on customer communication?

问题

我目前遇到一个问题,用户在注册时使用的电子邮件与他们结账时使用的电子邮件不同(他们的数据通过电子邮件地址存储在我们的后端)。然而,我不想阻止结账外的注册,因为(目前)我也允许在购买产品之前进行注册。我可以在结账时自动生成用户帐户,但如果他们之后使用另一个电子邮件进行注册,这并不会改变任何事情。

我目前的想法是添加一些方法来确保他们使用结账时的电子邮件进行注册。为此,我刚刚添加了一个功能,在结账时将他们的电子邮件保存在本地存储中,并在登录/注册页面上自动填充。然而,显然,只有在他们使用同一设备时才有帮助。我另一个想法是在我们门户网站的外部链接中包含一个令牌作为参数,这也会在其他设备上自动填充他们的电子邮件进行登录/注册。

**我的主要问题是上述斜体句子是否是不安全的做法。**我使用NextJS(以及React),因此其中一些内容是在服务器端呈现的,但我认为我必须在客户端处理该查询参数(?),在这种情况下,我可能必须创建一个未经身份验证的端点来将令牌转换为电子邮件。因此,任何截获URL的人(具有足够动机的人)也可以找出我的客户的电子邮件。这是一个合理的风险吗?我是不是过于多疑了?如果不是,有没有其他实现我所描述的期望结果的替代方法?我对各种想法持开放态度。

也欢迎对在本地存储中保存明文客户电子邮件的上述做法发表评论。

附加背景信息:我在身份验证方面使用Firebase,所有内容都是用TypeScript编写的。

英文:

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.

答案1

得分: 2

首先,如果任何东西接触到客户端,除非有证据证明否定,否则应该视为已被入侵。你不能依赖客户端代码。如果你这样做,你必须在服务器端重新验证。

让我们从一个普通的纯文本电子邮件链接开始。基本上有两个风险:

  1. 邮件泄露,导致您的客户收到垃圾邮件。
  2. 用户可能会将您发送的链接中的电子邮件更改为其他电子邮件。

但是风险是影响和可能性的因素。在互联网规模上,即使对于低价值资产,可能性也可以被认为是“几乎肯定”的。

这些事件发生的影响将指导您采取的控制措施以防止其发生。如果只是一个显示问题,您可能不需要做任何事情。说“欢迎(错误的电子邮件)”可能不值得修复的麻烦。

但是,如果您依赖此值进行某种形式的身份验证,则必须确保:

  • 它不能被伪造
  • 它不能被替换为另一个值
  • 它不能永远被重复使用

您可能还需要考虑以下要求:

  • 客户隐私得到保证
  • 它可以扩展

这可能是一个完整的项目,但用一个“令牌”替换电子邮件会起作用:

  • 生成一个类似于timestamp+email的字符串,可能还包括一些上下文,比如账号号码或其他内容。
    • 例如202308091543Z_user@gmail.com_123456
    • 分钟或小时的时间戳已足够。使用UTC时间
    • 使用当前时间,而不是过期时间作为时间戳,这样您可以在发送后更改过期容忍度,或者在出现问题时批量使旧令牌无效
  • 对其进行加密(这是一个棘手的部分,稍后详细介绍)
  • 对结果进行Base64编码,发送该令牌

当服务器(请记住,不能信任客户端)接收到令牌时,将执行以下操作:

  1. 解密令牌。如果解密失败,则拒绝(见下面的注释)
  2. 解析令牌。如果无法获取时间戳+电子邮件(和上下文),则拒绝
  3. 如果时间戳早于N天,则拒绝。30到120天似乎是合理的,但可以进行配置
  4. 对于合理性检查,可以加分
    • 时间戳不超过未来几分钟
    • 上下文与该电子邮件的数据库记录匹配
  5. 如果到达这一步,可以认为令牌是有效的

关于密钥管理的一些注意事项:

  • 支持密钥轮换。使用“当前”密钥进行解密,如果失败,则尝试使用“上一个”密钥。如果再次失败,则拒绝
  • 我建议使用AES-SIV或AES-GCM-SIV算法和模式,使用128位密钥。如果这样可以避免争论和会议,可以使用256位密钥。
  • 每个主机都需要访问相同的密钥(每个版本)
  • 您需要确保新密钥在可以使用之前可用,因此具有“当前”、“未来”和“上一个”密钥的方案将简化部署
  • 使用您的算法尝试使用“未来”密钥进行解密,并将其用作现在的“当前”密钥的信号
  • 在N天后删除“上一个”密钥(或在“未来”密钥变为“当前”后),因为您将拒绝那些令牌。
英文:

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.

答案2

得分: 1

这让我想起了邮件列表邮件底部的取消订阅链接。这些链接通常包含一个令牌作为查询参数,点击链接后可以进入一个页面,在不需要登录的情况下配置电子邮件通知设置。有时页面上会显示类似于“修改your-email@domain.com的电子邮件设置”的内容,据我理解,这与你所考虑的情况相同。如果这么多大公司都这样做而没有问题,也许你也可以这样做。

我的第二个想法是,如果恶意行为者拦截了这个URL,他们不是已经知道或能够知道客户的电子邮件地址吗?毕竟URL就包含在邮件中。

英文:

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?

huangapple
  • 本文由 发表于 2023年8月9日 16:30:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76865921.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定