Devise 3.1: Now with more secure defaults

We are glad to announce that Devise 3.1.0.rc is out. On this version, we have focused on some security enhancements regarding our defaults and the deprecation of TokenAuthenticatable. This blog post explains the rationale behind those changes and how to upgrade.

Devise 3.1.0.rc runs on both Rails 3.2 and Rails 4.0. There is a TL;DR for upgrading at the end of this post.

Note: We have yanked 3.1.0.rc and released to 3.1.0.rc2 which fixes some regressions. Thanks everyone for trying out the release candidates!

Do not sign the user in after confirmation

In previous Devise versions, the user was automatically signed in after confirmation. This meant that anyone that could access the confirmation e-mail could sign into someone’s account by simply clicking the link.

Automatically signing the user in could also be harmful in the e-mail reconfirmation workflow. Imagine that a user decides to change his e-mail address and, while doing so, he makes a typo on the new e-mail address. An e-mail will be sent to another address which, with the token in hands, would be able to sign in into that account.

If the user corrects the e-mail straight away, no harm will be done. But if not, someone else could sign into that account and the user would not know that it happened.

For this reason, Devise 3.1 no longer signs the user automatically in after confirmation. You can temporarily bring the old behavior back after upgrading by setting the following in your config/initializers/devise.rb:

config.allow_insecure_sign_in_after_confirmation = true

This option will be available only temporarily to aid migration.

Thanks to Andri Möll for reporting this issue.

Do not confirm account after password reset

In previous Devise versions, resetting the password automatically confirmed user accounts. This worked fine in previous Devise versions which confirmed the e-mail just on sign up, so the e-mail both confirmation and password reset tokens would be sent to were guaranteed to be the same. With the addition of reconfirmable, this setup change and Devise will no longer confirm the account after password reset.

Thanks to Andri Möll for reporting this issue and working with us on a fix.

CSRF on sign in

Devise’s sign in page was vulnerable to CSRF attacks when used with the rememberable feature. Note that the CSRF vulnerability is restricted only to the sign in page, allowing an attacker to sign the user in an account controlled by the attacker. This vulnerability does not allow the attacker to access or change a user account in any way.

This issue is fixed on Devise 3.1.0 as well as 3.0.2 and 2.2.6. Users on previous Devise versions can patch their application by simply defining the following in their ApplicationController:

def handle_unverified_request
  super
  Devise.mappings.each_key do |key|
    cookies.delete "remember_#{key}_token"
  end
end

Thanks to Kevin Dew for reporting this issue and working with us on a fix.

Store digested tokens in the database

In previous versions, Devise stored the tokens for confirmation, reset password and unlock directly in the database. This meant that somebody with read access to the database could use such tokens to sign in as someone else by, for example, resetting their password.

In Devise 3.1, we store an encrypted token in the database and the actual token is sent only via e-mail to the user. This means that:

  • Devise now requires a config.secret_key configuration. As soon as you boot your application under Devise 3.1, you will get an error with information about how to proceed;
  • Every time the user asks a token to be resent, a new token will be generated;
  • The Devise mailer now receives one extra token argument on each method. If you have customized the Devise mailer, you will have to update it. All mailers views also need to be updated to use @token, as shown here, instead of getting the token directly from the resource;
  • Any previously stored token in the database will no longer work unless you set config.allow_insecure_token_lookup = true in your Devise initializer. We recomend users upgrading to set this option on production only for a couple days, allowing users that just requested a token to get their job done.

Thanks to Stephen Touset for reporting this issue and working with us on a solution.

Token Authenticatable

Jay Feldblum also wrote to us to let us know that our tokens lookup are also vulnerable to timing attacks. Although we haven’t heard of any exploit via timing attacks on database tokens, there is a lot of research happening in this area and some attacks have been successful over the local network. For this reason, we have decided to protect applications using Devise from now on.

By digesting the confirmation, reset password and unlock tokens, as described in the previous section, we automatically protected those tokens from timing attacks.

However, we cannot digest the authentication token provided by TokenAuthenticatable, as they are often part of APIs where the token is used many times. Since the usage of the authenticatable token can vary considerably in between applications, each requiring different safety guarantees, we have decided to remove TokenAuthenticatable from Devise, allowing users to pick the best option. This gist describes two of the available solutions.

Thanks to Jay Feldblum for reporting this issue and working with us on a solution.

TL;DR for upgrading

As soon as you update Devise, you will get a warning asking you to set your config.secret_key. By upgrading Devise, your previous confirmation, reset and unlock tokens in the database will no longer work unless you set the following option to true in your Devise initializer:

config.allow_insecure_token_lookup = true

It is recommended to leave this option on just for a couple days, just to allow recently generated tokens by your application to be consumed by users. TokenAuthenticable has not been affected by those changes, however it has been deprecated and you will have to move to your own token authentication mechanisms.

Furthermore, the Devise mailer now receives an extra token argument on each method. If you have customized the Devise mailer, you will have to update it. All mailers views also need to be updated to use @token, as shown here, instead of getting the token directly from the resource.

With those changes, we hope to provide an even more secure authentication solution for Rails developers, while maintaining the flexibility expected from Devise.

6 responses to “Devise 3.1: Now with more secure defaults”

  1. Vermin says:

    If the attacker can access the confirmation e-mail, can’t he just confirm the account and then reset the password to get access?

  2. josevalim says:

    It depends on how he can access the confirmation e-mail. If he can access the target inbox, then surely. As well if he’s sniffing the target network. In those cases, there is nothing we can do. Now, in case he was mistakenly sent a confirmation e-mail (for example, reconfirmable), there is nothing he can do.

  3. In one of my apps I have some feature specs that relied on grabbing the confirmation/reset_password tokens directly off of the `User` in order to generate the correct URLs for confirming or resetting a password. Those specs suddenly broke.

    For now, I’ve resorted to extracting the “raw” tokens out of the sent emails. Is there a better way to do this? https://gist.github.com/stevenharman/6227508

  4. josevalim says:

    Steven, if you are sending the e-mail via the interface, there is nothing you can do. One would argue that your current approach is the most correct one, as you are effectively testing the proper e-mail is being sent too.

    In any case, you can always send the e-mail manually, which gives you access to the token. For recoverable, here is what you would call: https://github.com/plataformatec/devise/blob/master/lib/devise/models/recoverable.rb#L47

  5. Johnny says:

    Why would the user’s email randomly be in the parameters?

  6. HappyNoff says:

    Just a note, the article is missing the Devise tag 😉