How to Build Authentication in Rails
In this article we talk about several authentication methods in Rails from HTTP Basic Authentication to OmniAuth based authentications
Note: This article was originally published on the Launch School blog on 2014–09–14
Authentication is the process of establishing, and subsequently confirming, a site user’s identity. It is how an app recognizes, who you are. In this article, we’ll go through a few methods that you can add authentication to your Rails application. We’ll start with the HTTP Basic Authentication, look at the most commonly used username and password based local authentication, and then some less known alternatives.
1. HTTP Basic Authentication
In HTTP Basic Authentication a user’s credentials are stored in a HTTP header field, and are sent as a part of each (HTTP) request. Rails comes shipped with some utility methods that implement this, and it is about as easy to get working as it can get. If you want to ensure that every request for a web page checks for a single known user, it’s simply:
class ApplicationController < ActionController::Base
http_basic_authenticate_with name: "me", password: "@home"
end
There are a few variations on this theme, notably the authenticate_with_http_basic
method, which yields the given username and password to a block, which returns a true value if the supplied credentials are valid. This allows a bit more flexibility, for example.
class ApplicationController < ActionController::Base
USERS = { 'me' => '@home', 'you' => '@work' } authenticate_with_http_basic do |name, password|
USERS.has_key?(name) && USERS[name] == password
end
end
However, there are a number of security vulnerabilities in using these methods, to say the very least. Some of these are:
- Plain-text (or easily decodeable) passwords
- Having to send the name and password in every request
- ‘Spoofing’ attacks are easier
- Possible failures if a request is routed through a proxy server
Some other disadvantages are:
- The user is effectively anonymous
- It has no provision for registering new users
- You cannot explicitly log out — you have to shut down your browser to do this
- Changing names and passwords need a code update — which could prevent security breaches from being dealt with immediately
These built-in Rails methods are really for just logging in a small set of users, whose credentials (name and password), can be safely embedded (hard-coded) in the source code. Or, they can be used if site misuse is not expected, or if a little can be tolerated. Still, it can be useful for transient REST logins, feed subscriptions, or in private, and/or single user, and/or low security, sites. Perhaps on VPN, or intranet, hosted servers.
2. Authentication with Username and Password
Usually, authentication involves signing-up (registering) and signing-in (logging on), by means of a user-supplied name (or an email address) and password, which are entered in on-site forms. This is locally handled authentication, which means that it is your app that will carry out all of the processing to do with checking a user’s credentials.
Local authentication with username and password is pretty easy to set up, however the burden of keeping user credentials safe and secure is on the application developer, so make sure you take great care in storing and obfuscate the passwords.
Storing passwords
The users’ login details are, in most apps, stored in the same kind of database as all of the other data. To protect the users’ actual passwords from being visible, the passwords must be hashed before they are stored in a database. To verify a password, a hash (or a digest) algorithm is applied to a newly given password, which is the same algorithm that was applied to the originally given password, and stored as such in the database. Obviously, both of these have to be the same for the authentication attempt to succeed. Note that hashed data differs from encrypted data, because, with hashed data, there is no way of getting the unscrambled data back from the scrambled data. There is encoding, but no explicit decoding.
For additional security, a hashed password usually consists of two components, a salt (usually the first few characters), and a digest (i.e. the encrypted password). The salt is generated first, and is used, (along with the plain-text password), in the algorithm which generates the digest. Using a salt makes brute-force password cracking take longer.
Password cracking starts with a hashed password, (something like $2a$10$doESLXbv3vpYGJ9Eh76T8uYQW9ZxyrJZ.mWnSJ9JXvgB…). It then steps through a dictionary (of possible passwords), and for each word, it hashes it and compares it with the original. If they are equal, then that word can be used to wrest control a user’s account. Note that this word may not necessarily be the actual password — but only in theory, as the odds against are astronomical figures.
Here’s an example:
require 'digest/sha1'class PasswordDigester
SALT_SIZE = 3 def self.encode(password)
salt = "%0#{SALT_SIZE}x" % rand(16**SALT_SIZE) digest salt, password
end def self.check?(password, encrypted_password)
salt = encrypted_password[0, SALT_SIZE] encrypted_password == digest(salt, password)
end private def self.digest(salt, password)
salt + Digest::SHA1.hexdigest(salt + password)
end
end# For testing
if $0 == __FILE__
password = ARGV.shift encoded = PasswordDigester.encode password success = PasswordDigester.check? password, encoded puts encoded, success
end
However, the problem with the SHA1 hashing algorithm is that it is pretty quick, so though this approach is better, it could still be vulnerable to cracks with specialized hardware. If we use a computational expensive hashing algorithm, however, we can make this process even safer.
The bcrypt
algorithm is a much slower hashing algorithm and you can set its "work factor" to get how slow you want it to be. For example, if you set the work factor of bcrypt to 12, it can be 5 or so orders of magnitude (10,000 times) slower than SHA1.
Let’s see the same code using bcrypt. The bcrypt-ruby
gem takes care of salting automatically, so we don't have to do that explicitly.
require 'bcrypt'class PasswordDigester
def self.encrypt(password)
BCrypt::Password.create(password)
end def self.check?(password, encrypted_password)
BCrypt::Password.new(encrypted_password) == password
end
end# For testing
if $0 == __FILE__
password = ARGV.shift encrypted = PasswordDigester.encrypt password success = PasswordDigester.check? password, encrypted puts encrypted, success
end
Hiding passwords
The need of having to send the plain-text (or weakly encoded) login details during the initiation of the authentication process, is a security obstacle. So in order to stop a network sniffer from detecting a password during the transmission of a registration, or a login, request, it is essential to use the https protocol — not unprotected http.
Another potential security hazard is plain-text passwords appearing in the server logs. Server logs usually print a request’s parameters for every end-user request, and for logins and registrations, this can include the plain-text password.
Luckily, Rails, by default, filters out the value of any parameter whose name contains the characters ‘password’, so that’s one less worry. If you want filter other parameters by name, edit the file, config/application.rb and add the name to the array on the line:
config.filter_parameters += [:password]
# Replace the above line with something like this:
#config.filter_parameters += [:password, :authentication_token]
3. Authentication as a Service
Here, the authentication process is handed over to a trusted third party, such as Facebook, Twitter or Google. This is gaining popularity, and may come to displace the, locally supplied, name and password combination, which is used on most sites at present.
This is often done using the OAuth (OmniAuth) protocol, and its main advantages are that end-users don’t need to remember yet another password (for your particular site), and also that it is highly secure. The end-users simply sign in as they normally would for one of the (trusted) services, and the service sends a message back to your app, which states whether, or not, their attempt was successful. Your app has to handle the rest. After a successful login, you usually store a user’s id in a remembered session and redirect to a landing page for registered users. After a login failure you usually redirect back to the login form.
The data which is associated with the logged-in user, (like an email address or username), will probably be made available to you. Your app will also be given in return a number of unique tokens, the principal one of which identifies a particular user. Another token that is returned is a session token, which is associated with a particular login. This token is changed after each new login, and it could be useful in certain situations.
4. Token Authentication
Yet another method is token authentication, in which just a single data item, known as token, (something like 30703582f2f532a305c33d25bf8f40d6), is used to verify your identity.
This, in some ways, is safer than name and password combination, because the token can be continually changed. This means that an attacker, who manages to get hold of your token, will normally have very little time in which to act.
However, the main trouble with this method is synchronizing a token between an app and a particular end-user, and also mapping a token to a particular user. If something goes wrong at the client’s end, the interaction can break down irretrievably.
5. CSRF Token Authentication
This kind of token is not used for signing in, but it is widely used as a auxiliary authentication check. It prevents Cross-Site Request Forgery (CSRF) attacks. CSRF is temporary account hijacking, and is perpetrated by obtaining session cookies using injected JavaScript, which sends this cookie’s data to a server which saves it somewhere. An attacker cookie can then use this cookie to impersonate the real user, for some nefarious purpose.
CSRF protection works thus: for every server action, a new token is generated and sent as a part of the response you receive. In your subsequent request, you (implicitly) resend the token, and if it doesn’t match the very last one the server sent to you, your request gets aborted.
Note that Rails includes this by default. And it can be a source of ‘false negative’ bugs if you sign up on the same site in adjacent browser tabs.
5. LDAP Authentication
An LDAP (Local Directory Access Protocol) database, (which has fast reads and slow writes), is the most suitable kind of database for storing authentication information.
However, it requires a separate server that incurs maintenance, as well as development, overheads. But it is common in corporations and large-scale installations, especially for storing employee or client data in a central repository, that is accessed by various peripheral applications.
7. Additional Authentication Protection
Over and above the the entry of a name and password, some sites add further authentication procedures — more often to protect themselves rather than you!
A captcha (where a user is required to type in a character sequence embedded in an image) is to make sure a real person is registering. For example, if it weren’t for this, you could without a great deal of effort, feign popularity by automatically creating thousands of Twitter followers.
There is also Two-Factor Authentication, which is the ultimate in security, and is explained in this article. If you want to read more about it.