devise-two-factor icon indicating copy to clipboard operation
devise-two-factor copied to clipboard

"Remember this device for 30 days" feature

Open nlively opened this issue 6 years ago • 5 comments

One of the more useful features of common sites that use 2FA is that they provide an option to remember the device as a trusted device. Users are still prompted to log in with their username and password when the session ends, but the 2FA prompt won't be shown over and over again.

Seems like this would be different from the rememberable option in Devise, because users would always be prompted for username and password.

I didn't see anything in the documentation, so wanted to reach out and see if this has been done or if it is being worked on. If not, I can take this on and submit as a PR.

nlively avatar Jul 27 '18 22:07 nlively

@nlively can you submit a PR please?

phlegx avatar May 02 '19 12:05 phlegx

Is anyone working on this ? @nlively ?

TheCrazyMax avatar Apr 16 '20 19:04 TheCrazyMax

Wondering how to implement this in 2022.

brodyhoskins avatar Jan 13 '22 18:01 brodyhoskins

Here is how we implemented "Don't ask 2FA code for 30 days" on our web site. When the user is logged in with two-factor code, we first generate a random token and store it a secure cookie. The hash of the cookie is then saved in user's record along with the token's expiration date:

token = SecureRandom.alphanumeric(32)
token_expires_at = Time.current + 30.days

cookies.encrypted[:dont_ask_2fa] = {
  value: token,
  expires: token_expires_at,
  same_site: :strict,
  httponly: true,
  secure: Rails.env.production?
}

user.dont_ask_2fa_token_hash = BCrypt::Password.create(token)
user.dont_ask_2fa_token_expires_at = token_expires_at
user.save(validate: false)

Next time user is presented with 2FA signin screen, we get the token back from the cookie and check if it corresponds to the hash in the database. If it does, the 2FA screen is bypassed and the user is signed in:

return false if user.dont_ask_2fa_token_expires_at < Time.current # Token has expired

token = cookies.encrypted[:dont_ask_2fa]
return false if token.nil?
BCrypt::Password.new(user.dont_ask_2fa_token_hash) == token

Please let me know if there are any security issues with this approach.

evgenyneu avatar Oct 12 '22 23:10 evgenyneu

We have a similar implementation that uses a JSONB column (default: []) to store a list of token/expiry pairs. This allows multiple devices to be trusted simultaneously. The array is "cleaned" of expired tokens before use.

You might want to set a limit on the number of trusted devices, and/or delete a token cookie that cannot be matched (it could belong to another user).

barrywoolgar avatar Feb 06 '24 09:02 barrywoolgar