What should a verification email consist of?
How are you generating the 25 character string which you include in the URL? Is it completely random, or is it based off the current time, or the users email? It should be random and not guessable.
You should make sure the verification page actually renders (not just that a GET request occurred). Browsers such as chrome (and antivirus programs) often load URLs without the user explicitly clicking them as either a pre-fetch or to scan for security reasons.
That could result in a scenario where a malicious actor (Eve) wants to make an account using someone else's email (Alice). Eve signs up, and Alice received an email. Alice opens the email because she is curious about an account she didn't request. Her browser (or antivirus) requests the URL in the background, inadvertently activating the account.
I would use JavaScript on the page to verify the page actually rendered, and also include a link in the email where users can report that they did NOT create this account.
Adding to the Daisetsu's answer ( as I cannot write comments yet ).
It would not be bad to create a form and ask user to create password. this way, user would need to enter new password and after submitting it, account would be activated. It would solve the background antivirus check.
As for URL, it should be enough as long, as it's random string. There is very small chance that you will have 2 keys of same content at once.
Checking the IP would not be good as some ISPs may rotate IP addresses so you would block someone who wants to register but cannot.
Your email should include an activation code, not a link. You should not send any link used for account management in an email. If you (the legitimate party) email links (especially with long strings of random characters) to users, then you make it a little more difficult to distinguish legitimate emails from phishing attempts.
The activation code should consist of characters which can easily be copied and pasted (no spaces, '+', '-', etc.) and which are easy to manually enter as well. Letters and numbers work. The security of the activation code depends it being unpredictable. You derive the value using the system's secure random number generator, just as you should be doing with session IDs for cookies.
Activation codes should be stored in a database table, each row with a profile ID, the activation code*, and the expiration time. You don't want to put the profile ID or expiration time in a cookie, URL, or the activation code.** A malicious user could tamper with the ID to take over another account. You need to verify those things server-side because your security policy can't be blindly trusting client data.
The activation code should be submitted via a POST request. Automatically redirect users to the account activation URL (which can just be https://example.com/security/activate
) when they're done with the rest of the registration. Make it easy to access the account activation page again in case they accidentally close it.
You do not want a GET request to perform any action. A link might get visited without user interaction (due to browser prefetching or email security services). Furthermore putting secrets in a URL could leak sensitive data. (Through referral headers or browser addons.)
Don't worry about the IP, it still may change. (Same for user agent.)
I don't know if a cancellation feature is desirable. I don't even use the unsubcribe links in unsolicited emails because it may be a way to determine whether or not there is a human associated with an email address. If the activation code is long enough to resist brute force or if you limit the number of activation code attempts then I don't see the harm in just letting the activation code expire eventually.
On the other hand it could be nice to reduce the number of unwanted account activation emails. You can limit the number of emails you send to an address, to be polite. Say a maximum of 3 per day and a maximum of 5 per month. (I don't know if that's too high or too low.) As a non-user I would just make emails from your website auto-deleted or auto-marked-read.
There are a lot of variables to consider for unwanted activation emails. Most probably have more to do with user experience than security. You could go as far as providing a "cancel my activation and never send emails to this address because I'm never going to register" feature, but that has obvious problems. (You still need to verify the person's email too.)
* It doesn't hurt to hash the activation code. It's also not critical to hash, as with passwords, because each code has a one-time use, contains no private information, is randomly generated by the server, and expires.
** You could actually prevent tampering using a cryptographic MAC, but I would strongly discourage attempting that for most developers. Cryptography is hard to get right.