Create an account


Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tut] Double Opt-In Subscription Form with Secure Hash using PHP

#1
Double Opt-In Subscription Form with Secure Hash using PHP

Last modified on September 24th, 2019 by Vincy.

Do you know that the opening rate of emails by double opt-in confirmed subscribers is a staggering 40%? According to CampaignMonitor, email marketing generates $38 in ROI for every $1 spent.

Email marketing delivers the highest among any channel for marketing. Even in comparison with channels like print, TV and social media.

Double Opt-In Subscription Form with Secure Hash using PHP

Email marketing is the way to go. The primary mode to build your list is using a double opt-in subscription form.

What is inside?


  1. Why do we need double opt-in?
  2. What is the role of secure hash?
  3. Double opt-in subscription form in PHP
  4. Sequence flow for double opt-in subscription
  5. Double opt-in Subscription form UI
  6. PHP AJAX for subscription form submission
  7. URL with secure hash
  8. A PHP utility class for you
  9. Store subscription information to database
  10. A database abstraction layer for you
  11. Send confirmation email to users
  12. Subscription confirmation
  13. Conclusion

Use a double opt-in subscription form to signup to a newsletter, blog or a similar service. It has a two-step subscription process.

In the first step, the user will submit his name and email. Then the site will send an email to the user.

In the second step, the user will click the link in the received email. This will confirm his subscription to the site or service.

We call it the double opt-in because the users consent to the subscription twice. First by submitting the information and second by confirming to in by clicking the link in email.

Why do we need double opt-in?


It is the mechanism used to verify if the subscriber owns the input email. You need to do this verification because there is a chance for misuse by submitting emails that they do not own.

Double opt-in vs single opt-in is well debated and results arrived at. Double opt-in wins hands-on in every critical aspect.

What is the role of a secure hash?


In the confirmation email received by the user, there will be a link. This is the second and important step in the opt-in process. The link should be secure.

  • It should be unique for every user and request.
  • It should not be predictable.
  • It should be immune to a brute-force attack.

Double opt-in subscription form in PHP


I will present you a step by step detail on how to build a double opt-in subscription form with a secure hash using PHP.

You will get a production-grade code which you can use real-time in your live website. You can use this to manage your newsletter subscription.

I am releasing this code to you under MIT license. You can use it free even in commercial projects.

Sequence flow for double opt-in subscription


  1. Show a subscription form to the user.
  2. On AJAX submit, insert a new record in the database.
  3. Send an email to the user with a secure hash link.
  4. On click, the of the link, update the subscription status.
  5. On every step, there will be appropriate validations in place.

Double opt-in Subscription form UI


This is where developers get it wrong. Keep it simple and unobtrusive. For the high conversion, you must keep in minimal.

One field email is enough for the subscription. To address the user in a personal way, you need their name. That’s it. Do not ask for much information on a subscription form.

<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="author" content="Vincy"> <link rel="stylesheet" type="text/css" href="assets/css/phppot-style.css"> <title>Double Opt-In Subscription Form with Secure Hash using PHP</title> </head> <body> <div class="phppot-container"> <h1>Double Opt-in Subscription</h1> <form class="phppot-form" action="" method="POST"> <div class="phppot-row"> <div class="label"> Name </div> <input type="text" id="pp-name" name="pp-name" class="phppot-input"> </div> <div class="phppot-row"> <div class="label"> Email * <div id="email-info" class="validation-message" data-required-message="required." data-validate-message="Invalid email."></div> </div> <input type="text" id="pp-email" name="pp-email" class="required email phppot-input" on‌focusout="return validateEmail();"> </div> <div class="phppot-row"> <button type="Submit" id="phppot-btn-send">Subscribe</button> <div id="phppot-loader-icon">Sending ...</div> <div id="phppot-message"></div> </div> </form> </div> <script src="vendor/jquery/jquery-3.3.1.js"></script> <script src="assets/js/subscribe.js"></script></body> </body> </html> 

If you ask for much information, it will drive your users away. The same principle applies when you build a contact form. More or less these two behave in a similar aspect. Check how to build a contact form to know more on it.

Double Opt-in subscription form UI

You should leave the name field can as optional and only the email field should be as required. This will encourage the user to submit the form and subscribe for the newsletter.

Needless to say, the form should be responsive. Any page or form you build should work in mobile, tablet, laptop and desktop devices. You should optimize to work on any viewport.

Google parses webpages in mobile mode for indexing in the search result. The desktop is an old story and gone are those days. You should always design for the mobile. Make it mobile-first!

PHP AJAX for subscription form submission


I have used AJAX to manage the submission. This will help the user to stay on the page after subscription. You can position this subscription form in a sidebar or the footer.

Double Opt-in Subscription Form AJAX Submission

This is a classic example of where you should use the AJAX. I have seen instances where people use AJAX in inappropriate places, for the sake of using it.

Subscription AJAX endpoint


The AJAX endpoint has three major steps:

  1. Verify the user input.
  2. Insert a record in the database.
  3. Send an email with a link for subscription.

subscribe-ep.php is the AJAX endpoint. It starts with an if condition to check if the submit is via the POST method. It is always good to program for POST instead of the GET by default.

<?php use Phppot\Subscription; use Phppot\SupportService; /** * AJAX end point for subscribe action. * 1. validate the user input * 2. store the details in database * 3. send email with link that has secure hash for opt-in confirmation */ session_start(); // to ensure the request via POST if ($_POST) { require_once __DIR__ . './../lib/SupportService.php'; $supportService = new SupportService(); // to Debug set as true $supportService->setDebug(false); // to check if its an ajax request, exit if not $supportService->validateAjaxRequest(); require_once __DIR__ . './../Model/Subscription.php'; $subscription = new Subscription(); // get user input and sanitize if (isset($_POST["pp-email"])) { $userEmail = trim($_POST["pp-email"]); $userEmail = filter_var($userEmail, FILTER_SANITIZE_EMAIL); $subscription->setEmail($userEmail); } else { // server side fallback validation to check if email is empty $output = $supportService->createJsonInstance('Email is empty!'); $supportService->endAction($output); } $memberName = ""; if (isset($_POST["pp-name"])) { $memberName = filter_var($_POST["pp-name"], FILTER_SANITIZE_STRING); } $subscription->setMemberName($memberName); // 1. get a 12 char length random string token $token = $supportService->getToken(12); // 2. make that random token to a secure hash $secureToken = $supportService->getSecureHash($token); // 3. convert that secure hash to a url string $urlSecureToken = $supportService->cleanUrl($secureToken); $subscription->setSubsriptionKey($urlSecureToken); $subscription->setSubsciptionSatus(0); $currentTime = date("Y-m-d H:i:s"); $subscription->setCreateAt($currentTime); $result = $subscription->insert(); // check if the insert is success // if success send email else send message to user $messageType = $supportService->getJsonValue($result, 'type'); if ('error' != $messageType) { $result = $subscription->sendConfirmationMessage($userEmail, $urlSecureToken); } $supportService->endAction($result); } 

I have used the SupportService class to perform common functions.

Input sanitisation is a must. When you collect information using a public website, you should be careful. You could get infected without your knowledge. There are many bots foraging around the Internet and they click on all links and buttons.

To sanitise, do not invent a new function. Use the function provided by PHP and that is safe to use.

URL with secure hash


Generate a unique url for each user subscription. Use this url to confirm the user’s subscription in the second step. Remember, that’s why we call this double opt-in.

I have used a three step process:

  1. Generate a random string token.
  2. Convert the token to secure hash.
  3. Convert the secure hash to safe url.

I have used hexdec, bin2hex and openssl_random_pseudo_bytes to generate random bits. Which forms a random string.

Then to make the random string a secure hash, I have used the PHP’s built-in password_hash function. Never every try to do something on your own. Go with the PHP’s function and it does the job very well.

Before PHP 7, we had the option to supply a user generated salt. Now PHP 7 release has deprecated it. It is a good move because, PHP can generate a better salt than what you will generate. So stick to PHP 7 and use it without supplying your own salt.

The secure hash will contain all sort of special characters. . You can keep those special characters but need to url encode it. But I always wish to keep urls clean and the encoded chars do not look nice.

So no harm in removing them. So I cleanup those and leave only the safe characters. Then as a secondary precaution, I also encode the resultant string.

Thus after going through multi step process, we get a random, hash secure, safe, encoded URL token. Save the user submitted information in database record along with this token.

A PHP utility class for you


This is a utility class which I use in my projects. I am giving it away free for you all. It has functions that I reuse quite often and will be handy in situations. Every method has detailed comments that explain their purpose and usage method.

<?php /** * Copyright © 2019 Phppot * * Distributed under MIT license with an exception that, * you don’t have to include the full MIT License in your code. * In essense, you can use it on commercial software, modify and distribute free. * Though not mandatory, you are requested to attribute this URL in your code or website. */ namespace Phppot; class SupportService { /** * Short circuit type function to stop the process flow on validation failure. */ public function validateAjaxRequest() { // to check if its an ajax request, exit if not $http_request = $_SERVER['HTTP_X_REQUESTED_WITH']; if (! isset($http_request) && strtolower($http_request) != 'xmlhttprequest') { $output = $this->createJsonInstance('Not a valid AJAX request!'); $this->endAction($output); } } /** * Last point in the AJAX work flow. * Clearing tokens, handles and resource cleanup can be done here. * * @param string $output * @param boolean $clearToken */ public function endAction($output) { die($output); } public function setDebug($mode) { if ($mode == true) { ini_set('display_errors', 1); set_error_handler(function ($severity, $message, $file, $line) { if (error_reporting() & $severity) { throw new \ErrorException($message, 0, $severity, $file, $line); } }); } } /** * encodes a message string into a json object * * @param string $message * @param string $type * @return \JsonSerializable encoded json object */ public function createJsonInstance($message, $type = 'error') { $messageArray = array( 'type' => $type, 'text' => $message ); $jsonObj = json_encode($messageArray); return $jsonObj; } public function getJsonValue($json, $key) { $jsonArray = json_decode($json, true); return $jsonArray[$key]; } /** * If you are using PHP, this is the best possible secure hash * do not try to implement somthing on your own * * @param string $text * @return string */ public function getSecureHash($text) { $hashedText = password_hash($text, PASSWORD_DEFAULT); return $hashedText; } /** * generates a random token of the length passed * * @param int $length * @return string */ public function getToken($length) { $token = ""; $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $codeAlphabet .= "abcdefghijklmnopqrstuvwxyz"; $codeAlphabet .= "0123456789"; $max = strlen($codeAlphabet) - 1; for ($i = 0; $i < $length; $i ++) { $token .= $codeAlphabet[$this->cryptoRandSecure(0, $max)]; } return $token; } public function cryptoRandSecure($min, $max) { $range = $max - $min; if ($range < 1) { return $min; // not so random... } $log = ceil(log($range, 2)); $bytes = (int) ($log / 8) + 1; // length in bytes $bits = (int) $log + 1; // length in bits $filter = (int) (1 << $bits) - 1; // set all lower bits to 1 do { $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes))); $rnd = $rnd & $filter; // discard irrelevant bits } while ($rnd >= $range); return $min + $rnd; } /** * makes the passed string url safe and return encoded url * * @param string $str * @return string */ public function cleanUrl($str, $isEncode = 'true') { $delimiter = "-"; $str = str_replace(' ', $delimiter, $str); // Replaces all spaces with hyphens. $str = preg_replace('/[^A-Za-z0-9\-]/', '', $str); // allows only alphanumeric and - $str = trim($str, $delimiter); // remove delimiter from both ends $regexConseqChars = '/' . $delimiter . $delimiter . '+/'; $str = preg_replace($regexConseqChars, $delimiter, $str); // remove consequtive delimiter $str = mb_strtolower($str, 'UTF-8'); // convert to all lower if ($isEncode) { $str = urldecode($str); // encode to url } return $str; } /** * to mitigate XSS attack */ public function xssafe($data, $encoding = 'UTF-8') { return htmlspecialchars($data, ENT_QUOTES | ENT_HTML401, $encoding); } /** * convenient method to print XSS mitigated text * * @param string $data */ public function xecho($data) { echo $this->xssafe($data); } } 

Store subscription information to the database


Insert a record to the database on submission of the subscription form. We get the user’s name, email, generate a secure hash token, current time, subscription status.

<?php /** * Copyright © 2019 Phppot * * Distributed under MIT license with an exception that, * you don’t have to include the full MIT License in your code. * In essense, you can use it on commercial software, modify and distribute free. * Though not mandatory, you are requested to attribute this URL in your code or website. */ namespace Phppot; use Phppot\DataSource; class Subscription { private $ds; private $memberName; private $email; private $subsriptionKey; private $subsciptionSatus; private $createAt; private $supportService; function __construct() { require_once __DIR__ . './../lib/DataSource.php'; $this->ds = new DataSource(); require_once __DIR__ . './../lib/SupportService.php'; $this->supportService = new SupportService(); } public function getMemberName() { return $this->memberName; } public function getEmail() { return $this->email; } public function getSubsriptionKey() { return $this->subsriptionKey; } public function getSubsciptionSatus() { return $this->subsciptionSatus; } public function getCreateAt() { return $this->createAt; } public function setMemberName($memberName) { $this->memberName = $memberName; } public function setEmail($email) { $this->email = $email; } public function setSubsriptionKey($subsriptionKey) { $this->subsriptionKey = $subsriptionKey; } public function setSubsciptionSatus($subsciptionSatus) { $this->subsciptionSatus = $subsciptionSatus; } public function setCreateAt($createAt) { $this->createAt = $createAt; } /** * to get the member record based on the subscription_key * * @param string $subscriptionKey * @return array result record */ public function getMember($subscriptionKey, $subscriptionStatus) { $query = 'SELECT * FROM tbl_subscription where subscription_key = ? and subscription_status = ?'; $paramType = 'si'; $paramValue = array( $subscriptionKey, $subscriptionStatus ); $result = $this->ds->select($query, $paramType, $paramValue); return $result; } public function insert() { $query = 'INSERT INTO tbl_subscription (member_name, email, subscription_key, subscription_status, create_at) VALUES (?, ?, ?, ?, ?)'; $paramType = 'sssis'; $paramValue = array( $this->memberName, $this->email, $this->subsriptionKey, $this->subsciptionSatus, $this->createAt ); $insertStatus = $this->ds->insert($query, $paramType, $paramValue); return $insertStatus; } public function updateStatus($subscriptionKey, $subscriptionStatus) { $query = 'UPDATE tbl_subscription SET subscription_status = ? WHERE subscription_key = ?'; $paramType = 'is'; $paramValue = array( $subscriptionStatus, $subscriptionKey ); $this->ds->execute($query, $paramType, $paramValue); } /** * sends confirmation email, to keep it simple, I am just using the PHP's mail * I reccommend serious users to change it to PHPMailer and set * appropriate headers */ public function sendConfirmationMessage($mailTo, $urlSecureToken) { // following is the opt-in url that will be sent in email to // the subscriber. Replace example.com with your server $confirmOptInUrl = 'http://example.com/confirm.php?q=' . $urlSecureToken; $message = '<p>Howdy!</p> <p>This is an automated message sent for subscription service. You must confirm your request to subscribe to example.com site.</p> <p>Website Name: example</p> <p>Website URL: http://example.com</p> <p>Click the following link to confirm: ' . $confirmOptInUrl . '</p>'; $isSent = mail($mailTo, 'Confirm your subscription', $message); if ($isSent) { $message = "An email is sent to you. You should confirm the subscription by clicking the link in the email."; $result = $this->supportService->createJsonInstance($message, 'message'); } else { $result = $this->supportService->createJsonInstance('Error in sending confirmation email.', 'error'); } return $result; } } 

The reason for storing the current time is to have an expiry for every link. We can set a predefined expiry for the double opt-in process.

For example, you can set one week as expiry for a link from the moment you generate it. The user has to click and confirm before that expiry period.

Subscription status is by default stored as ‘0’ and on confirmation changed to ‘1’.

A database abstraction layer for you


It is my PHP abstraction for minor projects. This works as a layer between controller, business logic and the database. It has generic methods using which we can to the CRUD operations. I have bundled it with the free project download that is available at the end of this tutorial.

Send confirmation email to users


After you insert the record, send an email will to the user to perform the double opt-in confirmation. The user will have a link in the email which he has to click to confirm.

Keep the email simple. It is okay to have text instead of fancy HTML emails. PHP is capable of generating any email and you can code complex email templates. But the spam engines may not like it.

Subscription information database record with secure hash

If you wish to go with HTML emails, then keep the HTML code ratio to as least as possible. As this is also one factor using which the spam engines flag the emails.

Then remember not to use the spam stop words. There are words like “free”, “win”, “cash”, “promo” and “income”. There is a long list and you can get it on the Internet by searching for “email spam filter word list”.

I have used PHP’s mail() function to send the email. I recommend you to change it to PHPMailer to send SMTP based email if you plan to use this code in production.

Subscription confirmation


Create a public landing page and you may use .htaccess for a neat URL mapping. This URL should map with the URL sent to the user and the PHP file that is going to process the request.

As a first step, GET the token and to verify the user against the database. Check,

  1. if such a token exists,
  2. it is not expired,
  3. the user is not already subscribed
  4. add more validation as you deem fit.
<?php use Phppot\Subscription; use Phppot\SupportService; /** * For confirmation action. * 1. Get the secure has from url * 2. validate it against url * 3. update the subscription status in database accordingly. */ session_start(); // to ensure the request via POST require_once __DIR__ . '/lib/SupportService.php'; $supportService = new SupportService(); // to Debug set as true $supportService->setDebug(true); $subscriptionKey = $_GET['q']; require_once __DIR__ . '/Model/Subscription.php'; $subscription = new Subscription(); $result = $subscription->getMember($subscriptionKey, 0); if (count($result) > 0) { // member found, go ahead and update status $subscription->updateStatus($subscriptionKey, 1); $message = $result[0]['member_name'] . ', your subscription is confirmed.'; $messageType = 'success'; } else { // securiy precaution: do not reveal any information here // play subtle with the reported message $message = 'Invalid URL!'; $messageType = 'error'; } ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="author" content="Vincy"> <link rel="stylesheet" type="text/css" href="assets/css/phppot-style.css"> <title>Double Opt-In Subscription Confirmation</title> </head> <body> <div class="phppot-container"> <h1>Double Opt-in Subscription Confirmation</h1> <div class="phppot-row"> <div id="phppot-message" class="<?php echo $messageType; ?>"><?php echo $message;?></div> </div> </div> </body> </body> </html> 

If validation fails, do not reveal any information to the user. You should only say that it has failed.

Subscription double opt-in confirmation url verification

More important do not say, not such email found. This will allow finding who has subscribed to your service. Whenever a validation fails, the displayed message should not reveal internal information.

On validation success, update the subscription status. Then show a happy success message to the user.

Conclusion


I have presented you with a production-grade double opt-in subscription form. I have followed a most secure hash generation method for confirmation URL email. I present it to you under the MIT license. The intention is to be the most permissible. You can download it free and change the code. You can even use it in your commercial projects. I have used the most secure code as possible. You can use this in your live site to manage newsletter subscription. In the coming part, I will include unsubscribe and enhance it further. Leave your comments below with what sort of enhancements you are looking for.

Download

↑ Back to Top



https://www.sickgaming.net/blog/2019/09/...using-php/
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

[-]
Discord

[-]
Active Threads
News - Stardew Valley’s Latest 1.5.5 Upd...
Last Post: xSicKxBot
Today 07:10 AM
» Replies: 0
» Views: 1
News - Hood: Outlaws & Legends Season 2 ...
Last Post: xSicKxBot
Today 07:09 AM
» Replies: 0
» Views: 0
(Indie Deal) RE8, Frontier, Slitherine, ...
Last Post: xSicKxBot
Today 05:00 AM
» Replies: 0
» Views: 6
Microsoft - Inclusion for all this Inter...
Last Post: xSicKxBot
Today 02:39 AM
» Replies: 0
» Views: 5
News - .hack//G.U. Last Recode Is Coming...
Last Post: xSicKxBot
Today 02:39 AM
» Replies: 0
» Views: 3
News - A New Peacemaker TV Show Trailer ...
Last Post: xSicKxBot
Today 02:39 AM
» Replies: 0
» Views: 3
(Indie Deal) 2K, SNK, Ziggurat & Graffit...
Last Post: xSicKxBot
Yesterday 11:12 PM
» Replies: 0
» Views: 11
News - George Clooney Reveals Why He Tur...
Last Post: xSicKxBot
Yesterday 08:39 PM
» Replies: 0
» Views: 9
(Indie Deal) 2K, SNK, Ziggurat & Graffit...
Last Post: xSicKxBot
Yesterday 05:39 PM
» Replies: 0
» Views: 10
Microsoft - Fund manager RFP opens for M...
Last Post: xSicKxBot
Yesterday 05:39 PM
» Replies: 0
» Views: 3

[-]
Twitter

[-]
Sponsored
Get the Deal of the Week at RefurBees.com



Discord Server © SickGaming.net 2012-2021