Is devise's token_authenticatable secure?
Asked Answered
K

2

80

I'm building a simple api with Rails API, and want to make sure I'm on the right track here. I'm using devise to handle logins, and decided to go with Devise's token_authenticatable option, which generates an API key that you need to send with each request.

I'm pairing the API with a backbone/marionette front end and am generally wondering how I should handle sessions. My first thought was to just store the api key in local storage or a cookie, and retrieve it on page load, but something about storing the api key that way bothered me from a security standpoint. Wouldn't be be easy to grab the api key either by looking in local storage/the cookie or sniffing any request that goes through, and use it to impersonate that user indefinitely? I currently am resetting the api key each login, but even that seems frequent - any time you log in on any device, that means you'd be logged out on every other one, which is kind of a pain. If I could drop this reset I feel like it would improve from a usability standpoint.

I may be totally wrong here (and hope I am), can anyone explain whether authenticating this way is reliably secure, and if not what a good alternative would be? Overall, I'm looking for a way I can securely keep users 'signed in' to API access without frequently forcing re-auth.

Knowitall answered 4/9, 2013 at 4:14 Comment(0)
A
195

token_authenticatable is vulnerable to timing attacks, which are very well explained in this blog post. These attacks were the reason token_authenticatable was removed from Devise 3.1. See the plataformatec blog post for more info.

To have the most secure token authentication mechanism, the token:

  1. Must be sent via HTTPS.

  2. Must be random, of cryptographic strength.

  3. Must be securely compared.

  4. Must not be stored directly in the database. Only a hash of the token can be stored there. (Remember, token = password. We don't store passwords in plain text in the db, right?)

  5. Should expire according to some logic.

If you forego some of these points in favour of usability you'll end up with a mechanism that is not as secure as it could be. It's as simple as that. You should be safe enough if you satisfy the first three requirements and restrict access to your database though.

Expanding and explaining my answer:

  1. Use HTTPS. This is definitely the most important point because it deals with sniffers.

    If you don't use HTTPS, then a lot can go wrong. For example:

    • To securely transmit the user's credentials (username/email/password), you would have to use digest authentication but that just doesn't cut it these days since salted hashes can be brute forced.

    • In Rails 3, cookies are only shrouded by Base64 encoding, so they can be fairly easily revealed. See Decoding Rails Session Cookies for more info.

      Since Rails 4 though, the cookie store is encrypted so data is both digitally verified and unreadable to an attacker. Cookies should be secure as long as your secret_key_base is not leaked.

  2. Generate your token with:

    For an explanation on why this is necessary, I suggest reading the sysrandom's README and the blog post How to Generate Secure Random Numbers in Various Programming Languages.

  3. Find the user record using the user's ID, email or some other attribute. Then, compare that user's token with the request's token with Devise.secure_compare(user.auth_token, params[:auth_token]. If you are on Rails 4.2.1+ you can also use ActiveSupport::SecurityUtils.secure_compare.

    Do not find the user record with a Rails finder like User.find_by(auth_token: params[:auth_token]). This is vulnerable to timing attacks!

  4. If you are going to have several applications/sessions at the same time per user, then you have two options:

    • Store the unencrypted token in the database so it can be shared among devices. This is a bad practice, but I guess you can do it in the name of UX (and if you trust your employees with DB access).

    • Store as many encrypted tokens per user as you want to allow current sessions. So if you want to allow 2 sessions on 2 different devices, keep 2 distinct token hashes in the database. This option is a little less straightforward to implement but it's definitely safer. It also has the upside of allowing you to provide your users the option to end current active sessions in specific devices by revoking their tokens (just like GitHub and Facebook do).

  5. There should be some kind of mechanism that causes the token to expire. When implementing this mechanism take into account the trade-off between UX and security.

    Google expires a token if it has not been used for six months.

    Facebook expires a token if it has not been used for two months:

    Native mobile apps using Facebook's SDKs will get long-lived access tokens, good for about 60 days. These tokens will be refreshed once per day when the person using your app makes a request to Facebook's servers. If no requests are made, the token will expire after about 60 days and the person will have to go through the login flow again to get a new token.

  6. Upgrade to Rails 4 to use its encrypted cookie store. If you can't, then encrypt the cookie store yourself, like suggested here. There would absolutely be no problem in storing an authentication token in an encrypted cookie store.

You should also have a contingency plan, for example, a rake task to reset a subset of tokens or every single token in the database.

To get you started, you could check out this gist (by one of the authors of Devise) on how to implement token authentication with Devise. Finally, the Railscast on securing an API should be helpful.

Abydos answered 9/9, 2013 at 9:27 Comment(11)
Awesome, this helps a lot - thank you! This will almost definitely get the right answer. Bounty is yours if you add to this your opinion/recommendation (specifically) on the best way to handle API authentication : )Knowitall
What I described is the most secure way to handle API authentication, dunno what you mean by "best". But I'll try to be as specific as I can.Abydos
What's the benefit of SecureRandom.hex over say SecureRandom.urlsafe_base64?Giffie
While both methods generate a random string, urlsafe_base64 generates a url-safe string. It's all in the name. Unless you want to use your token in your url (which you shouldn't), use hex.Abydos
What is the timing safe alternative to User.find_by_auth_token(params[:auth_token])?Pilsner
user = User.find(id) or user = User.find_by_email(email) followed by Devise.secure_compare(user.auth_token, params[:auth_token]).Abydos
@Abydos this is a very good answer, but will you please clarify what you mean in your second point? It says "one-time random token" which I took to mean it should only be used one time and then be invalidated. But from the context of the rest of your answer I don't think this is what you mean. Also the phrase "cryptographic strength" isn't clear to me, I think you mean "cryptographically strong" or something right?Incubus
That is what I meant when I originally wrote this answer, but I edited that part out a while ago. I hope that is clearer now.Abydos
token != password. There is nothing wrong with storing a token in plain text. The problem with storing passwords in plain text is the password can be used somewhere else, that shouldn't be the case with your token.Tommyetommyrot
@Tommyetommyrot No. Token == password. If a hacker or a disgruntled employee has access to your database he should not be able to authenticate as a certain user or admin.Abydos
I disagree, if a hacker or disgruntled employee has access to your database, then tokens are the last thing you need to worry about. They already have your data.Tommyetommyrot
T
3

You can try to use rails4 with your API, it's providing more security and use devise 3.1.0rc

For token, session store you can go through http://ruby.railstutorial.org/chapters/sign-in-sign-out and http://blog.bigbinary.com/2013/03/19/cookies-on-rails.html for more understable.

At last you should go through these kind of encryption and decryption "Unable to decrypt stored encrypted data" to get the more security.

Tahmosh answered 11/9, 2013 at 13:6 Comment(1)
Does anyone have an example custom built alternative?Subinfeudate

© 2022 - 2024 — McMap. All rights reserved.