7

This question already has an answer here:

So I'm working on a PHP application called Magento and I would like to implement a link which will be sent in an email to customers that will automatically log them in.

I know it is critical in terms of security so here's what I came up with so far:

  • store the generated links in a database table
  • generate a random token value stored against the customer id
  • 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)
  • make the links one use only
  • set an expiration time on the links

I want to avoid the use of a second factor such as a PIN code to keep it as simple as possible for the customer but security matters so it's hard to find the right balance.

I know that Slack has a magic link sent by email when you don't want to type your password. The idea here would be something similar.

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 ?

marked as duplicate by Anders, S.L. Barth, techraf, Alexander O'Mara, WhiteWinterWolf Jul 13 '16 at 7:54

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

7

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.

  • Thanks a lot for that it is super helpful. One last thing re: the internal secure PHP random generator can I use openssl_random_pseudo_bytes(9) ? – Raph Petrini Jul 12 '16 at 12:41
  • 3
    Refer to official documentation for this. php.net/manual/en/function.openssl-random-pseudo-bytes.php It looks like you should use openssl_random_pseudo_bytes(9, true), but then again true may be the default. You could ask on StackOverflow to be sure. Whatever you do, do not set it to false... – Bryan Field Jul 12 '16 at 12:44
  • 1
    The second param to openssl-random-psueodo-bytes takes a reference to a variable, not a boolean value. You're supposed to check the value of the variable after the function has run in case you're not sure if your system has used strong cryptography. It doesn't change the behaviour of the prng. – bdsl May 14 '18 at 18:21

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