8

When a user subscribes to my newsletter via their email address, using php, how would I send them an 'Activation Link' via email to confirm it is their email address and not a fake one.

so at the moment I have

PHP:

<?php
 $to = "recipient@example.com";
 $subject = "Hi!";
 $body = "Hi,\n\nHow are you?";
 if (mail($to, $subject, $body)) {
   echo "<p>Message successfully sent!</p>";
  } else {
   echo "<p>Message delivery failed...</p>";
  }
 ?>

I guess i would change the $body to this:

$body = "Please click the link to activate your email \n
http://www.activationlink.com?";

How would I make it so that if a user clicked that link it would add their details to the Mysql database recognising they are a legitimate subscriber?

Any help or suggestions appreciated. Thanks

RSM
  • 14,540
  • 34
  • 97
  • 144
  • possible duplicate of [Generating confirmation code for an email confirmation](http://stackoverflow.com/questions/2088969/generating-confirmation-code-for-an-email-confirmation) – Gordon Jul 13 '10 at 13:14
  • 1
    @Gordon: not a dup, linked question only asks about generating random numbers. This one asked about the whole procedure from a higher level. – Borealid Jul 13 '10 at 13:18
  • @Borealid yes dup. The question body and the answers combined contain all there is to know to answer this question here. – Gordon Jul 13 '10 at 13:24

7 Answers7

13

What I like to do is:

  • Generate a unique, random ID in the registration process

  • Store the ID along with the E-Mail address, a "confirmed" field (default: "no") and any additional data in a database table

  • Send out the E-Mail with an URL pointing to activate the unique ID (e.g. domain.com/activate.php?id=102939505595

  • The activation page checks whether the unique key exists and changes the confirmed field to yes (or 1 or whatever).

  • Additionally and optionally, save the confirmation date/time, IP address and user agent.

Pekka
  • 442,112
  • 142
  • 972
  • 1,088
6

Insert the user into a table with a 'pending' flag set (or a 'validated' flag not set). They should not be able to do anything until the flag is changed. If you want to be really thorough, actually put them into a users_temp table. Generate a totally random key and associate it with their user ID. The link you email to them should be http://yourwebsite.com/?activate=totallyrandomkeyigeneratedearlier. When you get an activation request, turn on the valid flag for the user with the corresponding random key.

Borealid
  • 95,191
  • 9
  • 106
  • 122
  • 2
    In case you send out eMails when the account has been activated, consider deleting the id after the activation or check if it was previously activated. I had one system where calling the activation link would always trigger an eMail and one user had the URL opened in a browser tab for days. Whenever he restarted his browser and the tabs got restored, he would get a new activation confirmation. – Gordon Jul 13 '10 at 13:22
6

no database needed. you can send all data in the hyperlink signed by hash

I've answered similar question recently even with expiration time.
though it was for the password recovery link, but idea is the same

$token = sha1($time.$email.$salt).dechex(time()).dechex($user_id);
$link = "http://".$domain."/restorepass/?token=$token";

whole token would looks like single hexdecimal number and it would be hard to guess it's meaning.

upon receive just split and decode it back.
Neat, IMO.

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
  • You'll still need to store whether the user is activated or not. However, as pointed out, you don't need to store a hash. – Marcus Adams Jul 13 '10 at 14:35
  • @Marcus you can create user account at the time it's activated. that's all – Your Common Sense Jul 13 '10 at 15:00
  • That's a neat idea, and definitely low maintenance. The implication is that the hash is completely based on user supplied data, so it would need to be hashed against some secret key in order to avoid abuse, or, you said signed, so maybe some asymmetric algorithm. – Marcus Adams Jul 13 '10 at 15:18
  • Or even easier: Use your AES key to encrypt the users email address and use it as url-parameter (something like: servername/activate.php?address={email}&activationID={encrypted email}). When handling the request, just try to decrypt $_GET['activationID'] and compare it to $_GET['email']. This is probably one of the most secure approaches, and no information needs to be stored on the server, except for the AES-key. – Javaguru Jul 13 '10 at 15:36
  • @Marcus yeah some salt should be added. – Your Common Sense Jul 13 '10 at 15:53
1

Personally I would add there details to the database and have a fields called "active" then when they click the activation link all you need to do is update this one field.

You could also have a "This was not me" link in the email and if they click this you remove all there details.

Alistair Prestidge
  • 673
  • 3
  • 9
  • 19
1

Generate a unique ID and store this together with the username/password within some temporary database entry for the new user.

$tmpID = uniqid();

Then, adapt the link in you eMail-body to e.g:

$body = "Please click the link to activate your email \n
http://www.activationlink.com/activateAccount?activate=".$tmpID;

If the user requests /activateAccount on your server, check the database entry against the $_GET['activate'] parameter, and set the user activated (if it matches).

In order to make sure your database does not just get more and more entries, you could use a cron-job who clears entries older than e.g. 24h.

Javaguru
  • 890
  • 5
  • 10
  • This may be open to attack, since uniqid() is predictable. Make sure you use the more_entropy flag at least. – Marcus Adams Jul 13 '10 at 14:57
  • I agree, the *more_entropy* flag makes it more secure. But anyhow, if you don't have direct access to the server, it will be quite hard to determine the exact time! (Since uniquid() is based on the microseconds of the current time!!). So, if no direct access is possible, the attacker must try a lot of keys in order to find the correct one. Thus, a limitation of the number of allowed activation-trials for the account would probably be sufficient to prevent an attack, and should generally be considered as a good practice. – Javaguru Jul 13 '10 at 15:27
1

Firstly you will need to add 2 column to your database table that holds the users

The column should be called active and activation_hash

When the user registers you need to insert the user to the DB but set the active to 0 and the activation_hash becomes some random md5 of the users email_address,first_name etc with a unique_id() in there, make sure its in MD5 Format and then store that in the activation_hash column.

In your email template add the link for the user to activate such as:

<a href="http://mydomain.registrer.php?process=activate&id=<?php echo $user_id;?>&hash=<?php echo $activation_hash;?>">Activate your account</a>

Then inside your register file or wherever you your pointing the activation link to, just get the user_id & the activation hash via $_GET and validate against your db.

if they do not match then ask the user to enter his password to send another activation hash. otherwise set the column active to 1 so that the rest of your application knows what status the user is.

thats basically it.

RobertPitt
  • 56,863
  • 21
  • 114
  • 161
  • You should hash against a random value over simply a unique id. If the link includes the user id, you don't need it to be unique, and the MD5 hash guarantees that it's not unique anyway. It would be better to hash against a random value, or simply use a random value without the hash. – Marcus Adams Jul 13 '10 at 14:53
  • was that targeted at my post or was it just extra information, if so then I did explain that the has should consist of a array of data thats unique to that user as-well as some uniqe_id(); – RobertPitt Jul 13 '10 at 15:26
  • it's important to point out the difference between unique and random. A random key of sufficient length would be preferred over a unique one. – Marcus Adams Jul 13 '10 at 17:29
  • @Marcus, Your right, I should of gone into a little bit more depth. – RobertPitt Jul 14 '10 at 07:56
1

Here is my full solution scenario :

CREATE TABLE signup  (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(30) NOT NULL,
password VARCHAR(30) NOT NULL,
email VARCHAR(30) NOT NULL,
token VARCHAR(30) NOT NULL,
verified VARCHAR(50),
registration_date TIMESTAMP,
maxdate TIMESTAMP
);

Signup MySQL Table

Add signup page signup.php

Add the following form:

    <form name="signupform" method="post" action="process.php">
    Username:
    <input type="text" name="username">
    <br> Password:
    <input type="text" name="password">
    <br> Email:
    <input type="text" name="email">
    <br>
    <input type="submit" value="Signup">
</form>

Create process.php page:

<?php
$username = $_POST['username'];
$email = $_POST['email'];
$password = $_POST['password'];
date_default_timezone_set('America/New_York');
$registration_date = date('Y-m-d H:i:s');
$verified = 0;
$maxdate = date('Y-m-d H:i:s', strtotime($registration_date . ' +1 day'));
$salt = uniqid(mt_rand() , true);
$token = msha1(registration_date . md5($salt));
$sql = "INSERT INTO signup (username, password, email, token, verified, registration_date, maxdate) VALUES ('$username', '$password', '$email', '$token', '$verified', '$registration_date', '$maxdate')";

if (mysqli_query($conn, $sql))
    {
    $msg = 'Please click this link to verify your email: http://www.yourdomain.com/verifyemail.php?token=' . $token;
    mail($email, $subject, $msg);
    }
  else
    {
    echo mysql_error();
    }

?>

afterwards create verifyemail.php :

<?php
$token = $_REQUEST['token'];
date_default_timezone_set('America/New_York');
$current_time = date('Y-m-d H:i:s');
$sql = "SELECT * FROM users WHERE token='$token' AND maxtime >'$current_time' AND verified=0";
$result = mysqli_query($conn, $sql);
$notverified = mysqli_num_rows($result);

if ($notverified)
    {
    $sql = "update signup set verified=1 where token='$token'";
    $result = mysqli_query($conn, $sql);
    if ($result)
        {
        echo 'Email verified';
        }
      else
        {
        echo 'Error';
        }
    }
  else
    {
    echo 'Link expired';
    }

?>
wpcoder
  • 437
  • 5
  • 15