15

I am thinking that I create a deactivation code put that in the unsubscribe link along with their user id. Then when a recipient of my newsletter clicks the link, I can look up their user id and see if the deactivation code matches.

Does this sound like the best way?

What are some other ways?

3 Answers 3

16

Single-Click Unsubscribe

Implementing a single-click unsubscribe feature significantly enhances the user experience by allowing recipients to easily opt-out of newsletters or transactional emails. This approach not only meets legal requirements but also respects user preferences, potentially improving your brand's perception.

Utilizing Third-Party Email Solutions

Leveraging third-party email services is often the most straightforward and secure method for managing unsubscribe functionality. Services like SendGrid, MailGun, and Postmark offer built-in unsubscribe mechanisms that comply with legal standards and are user-friendly. These platforms handle the complexities of unsubscribe functionality, allowing you to focus on your core business activities.

Building Your Own System

If you prefer to implement your own unsubscribe system, it's essential to ensure it's secure and verifies that the user indeed wishes to unsubscribe. This verification prevents malicious attempts to unsubscribe users without their consent.

Strategy: Unpublished Per-User Identifier + App Secret

A straightforward method involves using a hash of a unique, unpublished user identifier combined with a rotatable global secret. This approach ensures that the unsubscribe link is specific to the user and cannot be easily guessed or brute-forced by attackers.

Example Implementation in JavaScript

const APP_SECRETS = [ '🦕 dinosaurs ⭐ 5975a2e459b480418e92da905ca839f3', '🍄 mushrooms 🌟 cbd8b97768188bbff79fbdfe4be77a2a', ]; function generateUnsubscribeUrlData(user) { return { id: user.email, hash: secureHash(user.uniqueUnsubscribeId + APP_SECRETS[0]) }; } function verifyUnsubscribeUrlData(db, email, hash) { const user = db.users.findByEmail(email); for (const secret of APP_SECRETS) { if (secureHash(user.uniqueUnsubscribeId + secret) === hash) { return true; } } return false; } 

Considerations:

  • Ensure your global app secret(s) are robust and rotatable.
  • Ensure your unique user identifiers are never published, nor potentially guessed.
  • Using multiple non-guessable pieces of user information can enhance security.

Strategy: Nonce Secret

A more secure, albeit less space-efficient, method involves generating a unique nonce for each unsubscribe email. This nonce is not stored directly but hashed using secure, salted hash functions (such as PHP's password_hash). The unsubscribe link contains the user's ID and the nonce, which is verified against the stored hash.

Benefits

  • Enhanced Security: By not storing the nonce directly and using a salted hash, this method offers superior protection against unauthorized unsubscribe attempts.
  • Compliance with Best Practices: This approach aligns with secure password storage techniques, applying them to the unsubscribe process.

Handling Expired Unsubscribe Links

Expired unsubscribe links, due to rotated secrets or deleted users, should redirect users to an email preferences page. Providing a seamless login redirect (e.g., /login?redirectTo=/user/email/preferences) ensures users can easily manage their email preferences even if the unsubscribe link has expired.

Email Best Practices

Obviously your email system should use all of the best practices for deliverability and security, including DKIM and SPF authentication.

Sign up to request clarification or add additional context in comments.

4 Comments

I know this is old but I'd be curious how a user can very quickly "brute force" the secret. In my case, emails are added as email recipients. We have no recipient accounts to look up. It seems like a hash using the email and a secret key would be fairy secure. No?
If the id and hash(id + secret) are known, the secret can be determined by brute force (just try every possible value for the secret until it matches the hash). This is why it’s better to use a nonce or unique secret for each user.
Thanks. Again, these aren't stored as users. They are just entered as email addresses. I'm using a GUID for my secret code. Would probably take a little while to try every possible value for a GUID.
True. If you use a 36 character password, today's computers will not be able to feasibly crack that. I will amend my answer! Also pick a base-64 or better password instead of a base-16 GUID: stackoverflow.com/a/40066621/554406
12

You could just use an hashing algorithm to secure the userID (so that nobody can unregister all your DB with a nasty loop).

You'll end up with two params : userID and hash.

The advantage is that you won't need to store any mapping between deactivation code and userID.

5 Comments

Let me get this straight... the userID param in this case would be hashed with the hash param. So the params are userID and hash, and then hash(hashedUserID,hash) = userID ....correct?
I think he means url = /unsub?userID=x&hash=$hash(x + secret), where secret is something you don't reveal.
Then I have to keep the secret in the database which would essentially be the deactivation code. So what's the advantage?
No database involved, because you'll have only one secret for all your application. The secret will only appear (once) in your code.
I would not include the userID from the DB, instead just use the email as this is an already exposed information in an email. See duplicate here: stackoverflow.com/questions/1240915/…
4

From a user perspective, do not require the user to input the e-mail address to unsubscribe. An approach that has all the information embedded in the link (such as you describe) is much better.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.