2

You know how all these websites send out links to their new users for them to verify their Email address? I'm trying to set up something like this but after some research I still haven't found a good explanation on how to implement this.

I'm open for plugin recommendations, however most of the plugins I found have a ton of other features that I don't really need.

Without using a plugin, how would I go about adding this to my code?

My approach would be to add a 'Email not verified' attribute to the user meta after signup and send out an email with some kind of verification key to the user. How can I verify if the user actually clicked on that link though?

Thanks for any advice

7

You can use user_register hook

add_action( 'user_register', 'my_registration', 10, 2 );
function my_registration( $user_id ) {
    // get user data
    $user_info = get_userdata($user_id);
    // create md5 code to verify later
    $code = md5(time());
    // make it into a code to send it to user via email
    $string = array('id'=>$user_id, 'code'=>$code);
    // create the activation code and activation status
    update_user_meta($user_id, 'account_activated', 0);
    update_user_meta($user_id, 'activation_code', $code);
    // create the url
    $url = get_site_url(). '/my-account/?act=' .base64_encode( serialize($string));
    // basically we will edit here to make this nicer
    $html = 'Please click the following links <br/><br/> <a href="'.$url.'">'.$url.'</a>';
    // send an email out to user
    wp_mail( $user_info->user_email, __('Email Subject','text-domain') , $html);
}

You can check for $_GET['act'] and then activate if that's a valid key by updating the meta value account_activated. You can use wp_authenticate_user hook to verify activation status every time when user tries to login.

Snippet to validate:

add_action( 'init', 'verify_user_code' );
function verify_user_code(){
    if(isset($_GET['act'])){
        $data = unserialize(base64_decode($_GET['act']));
        $code = get_user_meta($data['id'], 'activation_code', true);
        // verify whether the code given is the same as ours
        if($code == $data['code']){
            // update the user meta
            update_user_meta($data['id'], 'is_activated', 1);
            wc_add_notice( __( '<strong>Success:</strong> Your account has been activated! ', 'text-domain' )  );
        }
    }
}
| improve this answer | |
  • Thanks, that is really helpful! One thing though, how can I access the User ID and the $code from the url? I surely need to decode it somehow? – Philipp K Jun 30 '18 at 9:53
  • I updated the answer :) – Akshat Jun 30 '18 at 10:01
3

Guys I think the above method introduces security vulnerabilities that do not need to be there.

The primary goal of email verification is that we want people who signup to provide a real email address that they own or at least have access to. We do not want people signing up with lets say a random email address that someone else owns, for instance your email address.

The above code has vulnerabilities that could enable a hacker to register a random email address someone else owns and then relevantly easily brute force the $user_id value and the $code value on your email verification page.

1st vulnerability

You are using $user_id. Now I know this value could be any number of things but typically this is going to be an integer especially if using wordpress which is circa 30% of the websites on the internet and looking at the above php code it is indeed based on wordpress. The hacker is either given their $user_id as part of the signup process or else they guess their $user_id via brute force just going up sequentially starting at the number 1 and continuing 2 , 3, 4, 5, 6 ..... they will guess their $user_id in less than a day perhaps in even in less than an hour if your website does not have that many members.

2nd vulnerability

You are creating a $code using the MD5 hashing function and the time of signup. The hacker knows what time they signed up. Let's say the hacker signs-up at 3pm. Now all the hacker has to do is MD5 hash times from 2.55pm to 3.05pm and they will brute force the $code in less than an hour.

Looking at the above the hacker can just brute force the $user_id and $code in less than a day and verify an email address that they do not own

tut tut tut

A better approach would be to generate a $code with the rand() function using uppercase (A-Z) and lower case (a-z), numbers (0-9) and special characters e.g. (!&#). That MD5 hashing function is only using numbers 0-9 and lower case letters a-f and they way ye are using it based on the time of signup is making it incredibly easy to narrow down and brute force attack.

I have written the below PHP code the generate a random $code with Uppercase Letters/Lowercase Letters/Intergers/Special Characters. Don't make it so easy for hackers guys.

function generateRandomString($stringLength){



//specify characters to be used in generating random string, do not specify any characters that wordpress does not allow in the creation.

$characters = "0123456789ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_[]{}!@$%^*().,>=-;|:?";

//get the total length of specified characters to be used in generating random string

$charactersLength = strlen($characters);

//declare a string that we will use to create the random string 

$randomString = '';



for ($i = 0; $i < $stringLength; $i++) {

//generate random characters

$randomCharacter = $characters[rand(0, $charactersLength - 1)];

//add the random characters to the random string

$randomString .=  $randomCharacter;


};



//sanitize_user, just in case 

$sanRandomString = sanitize_user($randomString);



//check that random string contains Uppercase/Lowercase/Intergers/Special Char and that it is the correct length

if (
(preg_match('([a-zA-Z].*[0-9]|[0-9].*[a-zA-Z].*[_\W])', $sanRandomString)==1) &&
(strlen($sanRandomString)==$stringLength)

) 
{




//return the random string if it meets the complexity criteria 

return $sanRandomString;

}else {

// if the random string does not meet minimium criteria call function again 


return call_user_func("generateRandomString",($stringLength) );

}       

}//end of generateRandomString function



//call the function to generate a random string with Uppercase Letters/Lowercase Letters/Intergers/Special Characters 

//into the function we pass the length of string we require, in this exampe it will generate a string 32 characters long

$code = generateRandomString(32);

echo $code ;
| improve this answer | |
  • Great answer! It would be even a little better if you formatted the code a bit. Additionally the $ needs to be escaped. Also, correct me if I'm wrong but there's no reason to use call_user_func over calling the function directly. – Slbox Apr 15 at 0:25
  • The reason i am using call_user_function is the function is creating a random string to whatever length you specify. In the event that the random string complexity criteria is not met the function will just execute again automatically, for instance if the function generated a random string that did not contain an upper case letter or a number then call_user_function executes the function again. The function will only return a random string once it meets all the complexity criteria. – MikeMoy Apr 15 at 17:40
  • I have tested using base_64_encode on the $code generated by the random string function attach to $url then retrieve with GET method without issue. – MikeMoy Apr 15 at 17:48
  • Also very bad idea just to send the $code directly to the get_user_meta as this is same like storing plain text password in a database. You should be encrypting the $code stored in get_user_meta. Then de-crypt using the $code attached to the $url. Otherwise you just bypassed the whole point of wordpress security password hashing. Look at your wp-users, no passwords are stored in plain text, so you also need to hash the password reset $code before storing. Use function password_hash() to encrypyt and password_verify() to decrypt – MikeMoy Apr 15 at 18:00
  • I don't think that a user activation code necessarily needs to be hashed to be secure. If someone accesses your database, the user activation codes really don't matter, they can just set the email to confirmed, though they probably have a lot worse in mind. Regarding call_user_function, I'm still not understanding why you can't just call the function from itself. Can a function not call itself in PHP? – Slbox Apr 15 at 19:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.