Is devise's token_authenticatable secure? - ruby-on-rails

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.

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:
Must be sent via HTTPS.
Must be random, of cryptographic strength.
Must be securely compared.
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?)
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:
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.
Generate your token with:
SecureRandom.hex only if you are on Ruby 2.5+.
The gem sysrandom if you are on an older Ruby.
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.
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!
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).
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.
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.

You can try to use rails4 with your API, it's providing more security and use devise 3.1.0rc
In Rails 4.0, several features have been extracted into gems.
ActiveRecord::SessionStore
Action Caching
Page Caching
Russian Doll-caching through key-based expiration with automatic dependency management of nested templates.
http://blog.envylabs.com/post/41711428227/rails-4-security-for-session-cookies
Devise 3.1.0.rc runs on both Rails 3.2 and Rails 4.0.
http://blog.plataformatec.com.br/2013/08/devise-3-1-now-with-more-secure-defaults/
Devise is deprecation of TokenAuthenticatable in 3.1.0rc but you can build your own TokenAuthenticatable method for security issue. It's more reliable and secure.
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.

Related

How do sessions and cookies work in Rails?

I have been using Devise for a while to handle authentication on my Rails apps, but never really understood how it works. Because Devise also uses the session storage config set on Rails, I'm assuming this is a question on session handling with Rails.
Basically, I'm an auth newbie. I've read a few articles about authentication but most deal with abstracted libraries (they talk about engines, middle ware, etc) that don't make much sense to me. I'm really looking for lower level details.
Here's what I know so far..
I know about cookies and sessions. Cookies are strings stored on client-side which is used to maintain session across multiple HTTP requests.
Here's my basic understanding of authentication (please correct me if I'm wrong):
When user logs in, we send the SSL encrypted request to the server. If the credentials are valid, we save a random string called session id on the database (or any other data store) as the valid session id associated with a user id. This session id changes for each login/logout of the user.
After saving that session id on our data store, we return a response that asks the browser to set a cookie with the session id. This session id along with the user id would then be sent for successive request to the domain until it expires. For each request, our server would check the session id on the headers and verify if that session id is valid for that user id. If it is, then consider that user authenticated.
Here are my questions:
I've read that by default starting from Rails 2, it now uses CookieStore (instead of SessionStore) which generates session hashes with SHA512 (instead of session ids), and all this is stored on a cookie which means multiple user id's can literally have the same session hash and it would just work fine. It seems to me that this is a very dangerous thing, exposing a large number of hashes with a single secret key stored on the server and basing your entire authentication system based on this key. Is there a real world large scale application that uses hashing instead of storing server side session id's?
On the topic of storing active session id's on server side, I've also read that you can switch to use different kinds of session storage for Rails. Based on this, I've heard of systems moving authentication systems out as services and using auth tokens instead. What's an auth token and how does it differ from a session id?
Seems like I can just keep guessing a random string (for both hashing and server side sessions) to grab an existing session. Is there a way to protect against this? Is it normal to use more values stored on a cookie? (such as the username, real name or even another hash for authentication)
I know I'm asking a lot but I believe this will be useful for people like me who do not understand authentication and will be very useful to get a solid foundation about the topic.
I've read that by default starting from Rails 2, it now uses
CookieStore (instead of SessionStore) which generates session hashes
with SHA512 (instead of session ids), and all this is stored on a
cookie which means multiple user id's can literally have the same
session hash and it would just work fine. It seems to me that this is
a very dangerous thing, exposing a large number of hashes with a
single secret key stored on the server and basing your entire
authentication system based on this key.
Yeah, it seems scary at first blush, but I'm not sure what the danger really is. In Rails 4, session data is encrypted using PBKBF2, and then signed with your session secret. This signing helps detect if the contents of the encrypted session have been tampered and the server will reject the session if it detects tampering.
https://cowbell-labs.com/2013-04-10-decrypt-rails-4-session.html
If someone gains access to the session token (which is used to sign the session cookie), you likely have much bigger problems on your hands than end-users attempting to impersonate the wrong user.
Is there a real world large scale application that uses hashing
instead of storing server side session id's?
I honestly don't know the answer to this one offhand, but I suspect that the fact that this is the "default" for Rails means that there are more than a handful of sites out there using cookie session stores.
On the topic of storing active session id's on server side, I've also
read that you can switch to use different kinds of session storage for
Rails. Based on this, I've heard of systems moving authentication
systems out as services and using auth tokens instead. What's an auth
token and how does it differ from a session id?
I'm doing this on a server now - basically a random hash is generated when a user authenticates, and that hash is stored, encrypted and signed, in the cookie. The cookie hash is a key into a server-side datastore (in my case Redis, but it can be in a relational database or memcache or whatever you like), and the actual session data is the stored server-side mapped to that key. This leaves less of your session data in the hands of the client were people could potentially decrypt and analyze it, so it's generally a bit safer.
Seems like I can just keep guessing a random string (for both hashing
and server side sessions) to grab an existing session. Is there a way
to protect against this? Is it normal to use more values stored on a
cookie? (such as the username, real name or even another hash for
authentication)
Yes, you could do that, but it would take a very very long time. You would also need to guess how to sign the newly tampered cookie data so that it'd match what the server expects to see on its side, and it's signed with a pretty large key.
I really don't think there's much alternative for persisting authentication state to using cookies (I suppose HTML5 Local Storage would work if you're feeling exotic and don't care much about legacy browser support).

Are session, stored in cookies, safe enough to store important information like id of user?

I'm doing simple authentication. When the user had login, app stored user's id in session[:user_id]. I heard about sessions' encryption, but is it okay? I mean, can user change :user_id in his session to admin's id, for example?
The short answer is no, it's not a good idea.
Especially if people are accessing your site from a public location, or even worse, a public computer. The reason it's 'no' is because of Session Hijacking. You can read about Session Hijacking in section 2.3 of the Ruby on Rails security guide. You can get around this somewhat by using https (TLS/SSL). Refer to the guide for more information.
You could use OAuth or OpenID.
These options might be too hard and too long winded for your purpose.
There is a shortcut; to use an already existing well-known authentication and authorization framework like "Sign in With Google", which is essentially OAuth2 but you can use the tokens to give users with a google account access to your system. Here is the Ruby quick start guide.
Regarding encrypting session data, while a nice idea, it's still open for attack unfortunately. Definitely better than just storing id's in the sesssion data. Over at Information Security StackExchange you'll find some good commentary.
Do you need to encrypt session data?
...
... You don't need to encrypt it. At most, encryption is a form of
obfuscation. You're putting the key on the same system as the data, so
it can always be found and extracted.
ref: https://security.stackexchange.com/questions/18880/do-you-need-to-encrypt-session-data

Using Redis for authentication tokens

I am making a mobile app for a site with users, so in the app, users need to be able to sign in. To do this, I am creating a remember token to authenticate users on sign in. At first I was thinking about saving each token to the database and destroying it when the user signs out. When the user signs in, they would have the same token until they sign out. Then I saw somethings saying redis was the best way to store the tokens. My question is that if the only time a new token is created is when a user signs in and the only time it is destroyed is when a user signs out, is redis needed or is just saving it to the database ok performance wise
Redis will enable fast access to token data as it keeps the key value pair in memory.
Also, in case you need to expire token after a particular span of time Redis will allow you to set expiration time along with key .Thus you need not perform any explicit computation to determine its expiry time in order to delete it.
A use case of sessions using Redis is Magento.
That commerce framework relies heavily on caching, storing everything in Mysql until v.1.7
From that on, they embedded a model to store sessions in Redis.
I've used Redis with success. phpredis is the best client for php, since it's a c extension.
I would fiddle in heroku's for verifying performance.
(Now I CAN, because of hack support there hehe =)).
My two cents.

Encrypting (not hashing) and storing user passwords on a rails server, using devise cookies

Got a bit of an issue where I am required to maintain a secure connection with one server that proxies out requests to another, over basic authentication. However I can't be allowed to gain access to the password for the users who want to access the other server. Can anyone suggest a way to store the password (having been given it once) securely in say the session variable, encrypted by a key that only the client holds until the point when it's needed?
After a time it can expire, ie, you could give the username and password and half an hour would be an acceptable time to keep the credentials in case the user wanted to access the site again.
I've rewritten this a few times after producing pure waffle, sincerely sorry if the editing didn't make much difference.
If your server is going to be handling the password in plaintext (to talk to the other server with Basic auth), you're going to gain access to the password. Perhaps you want to avoid storing the password in plaintext?
Send the password in plain text to the server, which then encrypts it, stores the encrypted version and adds a cookie to the client with the key. Then any future requests provide the key to the server.
If you're looking for an encryption library, Recommended two-way encryption gems for Ruby?

Devise token encryption

I'm using Devise with the Token Authenticatable module. If I look at the data stored in the database, I see that even though the password is encrypted, the access token is not.
Isn't this a major security concern, since if a hacker ever got ahold of the database info, they would have the raw, completely functional access token, comparable to storing an unhashed password?
Security and convenience usually go on opposites sides. It is less secure, but there are ways to mitigate this (reducing convenience, of course). You could expire the token often (using expire_auth_token_on_timeout or periodically reset all tokens.
If your DB is compromised, you have bigger problems! Passwords are usually shared between services by users, so a leaked password is, by far, worst than a leaked auth_token.
Why are they not encrypted?
Passwords are stored in the user's mind, auth_tokens are generated and they will have to be stored somewhere (client-side) in order to be submitted and compared with the encrypted auth_token. A new token will have to be generated every time the user signs in with their "normal" credentials.
Performance might be an issue too. BCrypt is slow and doing this comparison on every request might present an issue.
I couldn't find a "official" answer in the documentation or by one of the developers, so the answer provided is based on my experience and opinion.

Resources