The token should have at least 72 bits of entropy (9 bytes, or 12 Base64 characters)
This should be generated with a Secure PSRNG (random number generator). So don't use the simple rand() operator of your programming language as it might be predictable, but look for the Secure random generator that PHP provides internally , or read bytes from /dev/urandom
store the generated links in a database table
It is best not to store the plain token in the database, in case the database is partially stolen . Instead, store an SHA-256 hash of the token, so that the only copy of the original token is found in the email you sent to the customer.
Note that Hashing is also common for passwords, but since passwords have less entropy you would normally use BCrypt. Since the tokens have 72 bits of entropy, the fast SHA hash will do fine.
both token and customer id in the URL
in case the token does not match the one stored for the customer id, reset the token (to avoid brute forcing)
I would say this is not needed. With 72 bits of entropy, it will be near-impossible to brute-force the token.
Also this scheme suggests that any unauthorized person could reset tokens this way, which is a form of DOS attack. (not hacking in, but preventing others from being able to use the system)
make the links one use only
This is a good choice, because the link will be stored in browser history unfortunately.
set an expiration time on the links
Certainly a good idea.
Do note however, that the weakest link is the ability for someone to hack your customer's Email (which is usually synced across multiple devices), so this is not sufficient security for many situations, but may be acceptable in your case. That is your decision.
I'm looking for any recommendation on this approach, and how can I improve it ? What did I miss ? What are the risks left with such approach and how to overcome them ?
I've outlined what you missed above and the primary risk of using this approach overall. However I would add that you should limit the amount of emails a person will receive. You don't want a robot filling out your Forgot Password form too many times thereby spamming your customer.
There are many other security considerations related to session management as well as authentication itself but that is beyond the scope of this answer.
openssl_random_pseudo_bytes(9)? – Raph Petrini Jul 12 '16 at 12:41openssl_random_pseudo_bytes(9, true), but then againtruemay be the default. You could ask on StackOverflow to be sure. Whatever you do, do not set it to false... – 700 Software Jul 12 '16 at 12:44