Ed91gg
Ed91gg

Reputation: 307

Proper way to set "unsubscribe" link from a sent email to users?

I've been using a way to unsuscribe people from a website newsletter, and that works when a user replies to an email with the word "unsuscribe".

But I want to change that so people only click a link in the email they received, redirect to my website and succesfuly unsuscribe that user. But this way makes me afraid of people being able to play with the url, write different emails and unsuscribe random people.

I know it sounds crazy but I'm a bit paranoic with the security stuff.

I have no code since I have no idea how to achieve this the proper way, meaning no playing with the url parameters.

Thanks!

Upvotes: 2

Views: 2210

Answers (2)

rob
rob

Reputation: 1

I have just seen the follwing: The link in the email to unsubscribe.php The user keys in his email again, confirms it in his email box. I see, the newsletter app at www.mywebtodo.net could be a bit better.

Upvotes: 0

Lawrence Cherone
Lawrence Cherone

Reputation: 46620

Sign the email with hash_hmac().

$token = hash_hmac('sha256', '[email protected]', 'secret-server-key');

Then pass that to the email template, so your unsubscribe link would be somthing like:

<a href="http://example.com/[email protected]&token=abcec5cdca4d760d34e8c02107ae68f251f08aaa9dd14956c2f0ab84a33f6441">

Then when the user clicks it, sign the email parameter, if the tokens match then continue.

<?php
if (isset($_GET['email'], $_GET['token']) && 
    $_GET['token'] === hash_hmac('sha256', $_GET['email'], 'secret-server-key')
) {
  // lagit
}

This is safe as long as you never share the secret-server-key.


Edit:

Taking on the comments from @FunkFortyNiner

You could sign and encode the email into a single token. Essentially just like JWT now but without the json ;p

So your token would look like: ZW1haWxAZXhhbXBsZS5jb20.abcec5cdca4d760d34e8c02107ae68f251f08aaa9dd14956c2f0ab84a33f6441 but would involve alot more code in checking. If your really paronoid perhaps its worth it.

<?php
$email = '[email protected]';

function base64url_encode($str) {
    return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
}

function base64url_decode($str) {
    return base64_decode(strtr($str, '-_', '+/'));
}

$token = base64url_encode($email).'.'.hash_hmac('sha256', $email, 'secret-server-key');

// mock got token
$_GET['token'] = $token;

// when checking
if (isset($_GET['token'])) {
    $tok = explode('.', $_GET['token']);

    // check token parts
    if (count($tok) !== 2) {
        // not valid token
        throw new \Exception('Invalid token!');
    }

    // check email segment
    if (!isset($tok[0]) || !filter_var(base64url_decode($tok[0]), FILTER_VALIDATE_EMAIL)) {
        // not valid email
        throw new \Exception('Invalid token!');
    }

    $email = base64url_decode($tok[0]);

    if ($tok[1] !== hash_hmac('sha256', $email, 'secret-server-key')) {
        // failed verification
        throw new \Exception('Invalid token!');
    }

    //
    echo 'Its lagit!';

    // do somthing with $email
}

https://3v4l.org/tcqk8

Upvotes: 4

Related Questions