HOW-TO: Build a secured web application with Ruby on Rails
May 7th, 2006
Most of today’s web applications are secured. Namely, to get to your information (say in deli.cio.us), you need to register an account, and then login each time you want to update or access your information. This how-to will take you through the steps necessary to start-up a secured web application.
NOTE: I appologize up front for the poor code formatting. My pretty-print editor simply didn’t want the code to look good and kept undoing all the nice formatting I did. Still, I figured it would better for you to have stuff you can copy and paste than a graphic. Just copy the code, throw it into eclipse, and format it there.
When you are finished you will have:
- A login page
- A register page
- A development database
- Basic SHA1 encryption enabled (with a little salt thrown in)
- A project as shown to the left
This how-to is based off examples in Agile Development with Rails, and the Rails Recipies cookbook. I highly recommend getting the PDFs of the beta of both books.
Here is an overview of what we will do:
STEP 1: Design the application
STEP 2: Create the project and database
STEP 3: Build our user model
STEP 4: Generate the account controller
STEP 5: Build all the account views
STEP 6: Generate the workbench controller and view
STEP 7: Enable the security
STEP 8: Throw in an admin scaffold for good measure
This tutorial assumes you are using mySql as your database and have already downloaded and setup a development environment for rails. If you haven’t, you can check out my HOW-TO on setting up eclipse for Ruby on Rails development.
STEP 1: Design the application
I think this looks good, what do you think?

Our main entry point will be the login screen. We will need to throw some validation on it. If there are any errors, we will just use the same page. If login works, we will take them to a ‘workbench’ page. This is a placeholder the secured home page of your app.
We also need to let them register an account if one doesn’t exist. So, we need a register page. If the registration works, we just log them in under the covers and take them to the workbench page. If there are errors, we will just use the register page to display them.
Our workbench page will be pretty simple. It just needs to say a little hello message to the user and we probably should throw a log out button on there too. I don’t have the logout shown on the diagram, but on a logout, we will just take them to the login page.
For good measure, we should thrown in a admin scaffold. This will be a bare bones admin app that will let us add and delete users. It is very helpful when testing and you usually need some sort of admin functionality by the time you go into production, so this will serve as a decent placeholder unit you get those requirements figured out.
Ok, so what we have is pretty simplistic. But it is a good starting point and gets your basic infrastructure in place. It has good visibility, which is good for generating all those feature requests you will need to start implementing.
STEP 2: Create the project and database
Ok, fire up eclipse and create a new Rails project. For the sake of this tutorial, lets assume you are calling it SecuredApp. Though, for heaven’s sake, if you are following this step-by-step, don’t really call it that. It’s a horrible name for an application. Think of an app you would really want to build and call it that.
Now open a command line shell and make sure your database is up and running. If it is not, time to start it up (note the startup directory and command varies slightly from distribution to distribution, so only use this if this is how it is done on your distribution — bottom line, start up the db
):
rubyuser@linux:~/workspace/SecuredApp> su
Password:
linux:/home/rubyuser/workspace/SecuredApp# /etc/init.d/mysql start
Starting service MySQL linux:/home/rubyuser/workspace/SecuredApp # exit
exit
Now we need to create our database. Note that the root account for some mySql installations is called ‘root’ and ‘admin’ for others. As you can tell from the example, mine is ‘root.
rubyuser@linux:~/workspace/SecuredApp> mysqladmin -uroot -p[put root password here] create SecuredApp_development
All the examples have you using root and an empty password. I don’t recommend you doing this as it is not a good habbit to get in. Instead, create a user that your app will use and grant that user access to the database. For this example, I will create a database user ‘rubyuser’ with the very unsecure password of ‘PASSWORD’
rubyuser@linux:~/workspace/SecuredApp> mysqladmin -uroot -p[root password] create AgendaRedux_development
rubyuser@linux:~/workspace/SecuredApp> mysql -uroot -p[root password]
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2 to server version: 4.0.18
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> grant all on SecuredApp_development.* to 'rubyuser'@'localhost' identified by 'PASSWORD';
Query OK, 0 rows affected (0.06 sec)
Ok, now we need to let rails now about it. Go into eclipse and open SecuredApp/config/database.yml. Update the configuration with the username and password you just granted rights for.
# MySQL (default setup). Versions 4.1 and 5.0 are recommended.
#
# Install the MySQL driver:
# gem install mysql
# On MacOS X:
# gem install mysql — –include=/usr/local/lib
# On Windows:
# There is no gem for Windows. Install mysql.so from RubyForApache.
# http://rubyforge.org/projects/rubyforapache
#
# And be sure to use new-style password hashing:
# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
development:
adapter: mysql
database: SecuredApp_development
username: rubyuser
password: PASSWORD
host: localhost# Warning: The database defined as ‘test’ will be erased and
# re-generated from your development database when you run ‘rake’.
# Do not set this db to the same as development or production.
test:
adapter: mysql
database: SecuredApp_test
username: rubyuser
password: PASSWORD
host: localhostproduction:
adapter: mysql
database: SecuredApp_production
username: rubyuser
password: PASSWORD
host: localhost
One of these days I really do need to click on that “And be sure to use new-style password hashing” link.
STEP 3: Build our user model
Lets start by generating the User model:
rubyuser@linux:~/workspace/SecuredApp> script/generate model User
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/user.rb
create test/unit/user_test.rb
create test/fixtures/users.yml
create db/migrate
create db/migrate/001_create_users.rb
Now, lets update that migrate script to create our user table. In eclipse, open up SecuredApp/db/migrate/001_create_users.rb and edit it as follows:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|t.column “username”, :string
t.column “password_salt”, :string
t.column “password_hash”, :stringend
end
def self.down
drop_table :users
end
end
FYI: I thinking I have a convention error here in not naming the username field user_name. Though I would have to update all my examples at this point to fix it. I will leave that as an ‘exercise for the student’
Now lets run the script. In your shell:
rubyuser@linux:~/workspace/SecuredApp> rake migrate
(in /home/rubyuser/workspace/SecuredApp)
== CreateUsers: migrating =======
– create_table(:users)
-> 0.0099s
== CreateUsers: migrated (0.0101s) =
Now it is time to build our user model. In eclipse, open SecuredApp/app/models/user.rb. Edit is follows:
class User < ActiveRecord::Base
# make sure we have the required
# fields we need when saving
validates_presence_of :username,
:password,
:password_confirmation# make the name unique as its going to
# be the login
validates_uniqueness_of :username# we want the password to be at least 5 characters
validates_length_of :password,
:minimum => 5,
:message => “should be at least 5 characters long”# make sure that password_confirmation and password match
attr_accessor :password_confirmation
validates_confirmation_of :password# lookup the user and check the password
# set user to nil if user doesn’t exist
# or password doesn’t match
def self.login(username, password)user = User.find(:first, :conditions => [’ username = ?’ , username])
if userexpected_password = encrypted_password(password, user.password_salt)
if user.password_hash != expected_passworduser = nil
end
end
userend
# normally for virtual attributes we
# just need to declare:
# attr_accessor: [fieldname]
# to create the getter and setter
# since password has extra logic in
# the setter, we have to create them
# by hand# password getter
def password@password
end
#password setter
def password=(pwd)
@password = pwd
create_new_salt
self.password_hash = User.encrypted_password(self.password, self.password_salt)end
# make sure we leave at least one user in the database
def safe_deletetransaction do
destroy
if User.count.zero?raise “Can’t delete last user”
end
end
end
private
# create the salt we will use when encrypting the password
def create_new_saltself.password_salt = [Array.new(6){rand(256).chr}.join].pack(”m”).chomp
end
# returs the hash for the password using the salt provided
def self.encrypted_password(password, salt)string_to_hash = password + salt
Digest::SHA1.hexdigest(string_to_hash)end
end
The books do a good job explaining what this logic is doing (and hopefully my comments are helpful
At this point, lets test out our user model object. We can do this in the ruby console. Go to your console window run the following:
rubyuser@linux:~/workspace/SecuredApp> ruby script/console
Loading development environment.
Now create a new user:
>> standon = User.new(:username => “standon”)
=> #nil, “username”=>”standon”, “password_hash”=>nil}>
Set the password:
>> standon.password = “secret”
=> “secret”
Check the password_hash and password_salt properties. Notice how they were set by the password setter method we wrote:
>> standon.password_hash
=> “d357cb1a9d6bff0393b4ad8f338e3f29b24a16e3″
>> standon.password_salt
=> “+RH7rfvB”
Now try to save the user
>> standon.save
=> false
Why does it say false? Could it be that we didn’t se the password_confirmation property?
>> standon.password_confirmation = “secret”
=> “secret”
>> standon.save
=> true
Looks like that was it. Now check the login method:
>> User.login(”standon”, “secret”)
=> #”standon”, “password_salt”=>”+RH7rfvB”, “id”=>”3″, “password_hash”=>”d357cb1a9d6bff0393b4ad8f338e3f29b24a16e3″}>
Everything is looking good. Time to move on to the next step.
STEP 4: Generate the account controller
Exit out of that ruby console and fire up the generate script again. We want to generate a skeleton for an account module with the basic actions we need to support:
rubyuser@linux:~/workspace/SecuredApp> ruby script/generate controller Account register login logout
exists app/controllers/
exists app/helpers/
create app/views/account
exists test/functional/
create app/controllers/account_controller.rb
create test/functional/account_controller_test.rb
create app/helpers/account_helper.rb
create app/views/account/register.rhtml
create app/views/account/login.rhtml
create app/views/account/logout.rhtml
We won’t need the logout.rhtml, so go into eclipse and delete it. Now, in eclipse, enter the following in as the code for SecuredApp/app/controllers/account_controller.rb:
class AccountController < ApplicationController
layout “account”
# rerout the index to profile page when it exists
# for now, just route it to the workbench
def indexredirect_to(:controller => “workbench”, :action => “index”)
end
# on a get request, just display the login page
# on a post request, attempt the login
# redisplay the login page if unsucessful
# otherwise, redirect to the workbench
def loginsession[:user_id] = nil
if request.post?user = User.login(params[:username], params[:password])
if usersession[:user_id] = user.id
redirect_to(:controller => “workbench”, :action => “index”)else
flash[:notice] = “Invalid user/password combination”
end
end
end
# on a get request, just diplay the registration page
# on a post, attempt to save the registration
# redisplay the page if unsucessful
# otherwise, redirect to the workbench
def registerflash[:notice] = “”
@user = User.new(params[:user])
if request.post? and @user.savesession[:user_id] = @user.id
redirect_to(:action => “index”)end
end
# kill the session and go back to the login page
def logoutsession[:user_id] = nil
flash[:notice] = “Logged out”
redirect_to(:action => “login”)end
end
Again, reference the books for an in depth discussion of the logic. Before we can try this out, we need to get some views built. So, on to the next step.
STEP 5: Build all the account views
So you see how that first line of the account controller refers to an account layout? I think we should make one otherwise it may not run so well. In eclipse, go to the SecuredApp/app/views/layouts folder and create the file account.rhtml. Its contents should be (sorry it’s an image, this wordpress editor, which is normally great for articles, is not so kind on code and especially html code):

Now, we have two actions that can end up passing through to their view. If you examine the index and logout action, you will see that they actually redirect to other actions.
Lets start with the login view. In eclipse, open SecuredApp/app/views/account/login.rhtml and edit it as follows:

Now open SecuredApp/app/views/account/register.rhtml and edit it as follows:

In both of the above views, make sure to change the div class from “redux_form” to one of your own stylesheet classes. I based my stylesheet off of the depot.css file from the book. In fact, all I did was change all references of depot_form to redux_form. Anyway, we are almost ready to go. In fact, you can test out going from the login page to the registration page. Though if you actually try to login and register, it will complain about not having workbench stuff, so on to the next step.
STEP 6: Generate the workbench controller and view
Open up your console again and lets run the generate script to create the workbench controller
rubyuser@linux:~/workspace/SecuredApp> script/generate controller workbench
exists app/controllers/
exists app/helpers/
create app/views/workbench
exists test/functional/
create app/controllers/workbench_controller.rb
create test/functional/workbench_controller_test.rb
create app/helpers/workbench_helper.rb
In eclipse, open SecuredApp/app/controllers/workbench_controller.rb and edit as follows:
class WorkbenchController < ApplicationController
layout “workbench”
def index
@user = User.find(session[:user_id])
end
end
Now go to SecuredApp/app/views/layouts and create workbench.rhtml and edit as follows:

Now open SecuredApp/app/views/workbench/index.rhtml and edit as follows:

You should now be able to run everything. And since I haven’t mentioned it, to test out the app, make sure you launch webrick with:
rubyuser@linux:~/workspace/SecuredApp> script/sever
And then you can open a browser and enter http://0.0.0.0:3000/account/login to test your app. Hopefully everthing worked find. There is only one problem, the secured pages are not secured. And I think that was the whole point of this little how-to. On to the next step we go!
STEP 7: Enable the security
The fact that this step is so easy gives me great comfort as I evaluate Rails as a development platform. It is often the case in large scale development that you have to inject controller behavior after the fact. Rails has a well defined methodology for doing this, and we will use it right now. First thing we will do is add a couple of controller filters to our application. In eclipse open up SecuredApp/app/controllers/application.rb. Edit is as follows:
# Filters added to this controller will be run for all controllers in the application.
# Likewise, all the methods added will be available for all controllers.
class ApplicationController < ActionController::Basedef authorize
unless User.find_by_id(session[:user_id])
flash[:notice] = “Please log in”
redirect_to(:controller => “account”, :action => “login”)end
end
def isAdmin
unless User.find_by_id(session[:user_id]) and User.find_by_id(session[:user_id]).username == “admin”
flash[:notice] = “[Waves hand] You are not the admin I am looking for”
redirect_to(:controller => “account”, :action => “index”)
end
end
end
The isAdmin one is a bit hokey, but it is only for our admin scaffold anyway. It’s a starting point. The first filter checks if the user is logged on and only lets through those users that are. The second filter not only checks to see if they are logged on, but it also makes sure that that user’s name is ‘admin’. Of course these filters won’t be of much use if we don’t use them. Open up SecuredApp/app/controllers/account_controller.rb and add the following code right before layout as shown:
before_filter :authorize, :except => [:login, :register]
layout “account”
We need to make sure to do the same to our workbench controller. open SecuredApp/app/controllers/workbench_controller.rb and add this to the top:
before_filter :authorize
Try it out now. Try going to http://0.0.0.0:3000/workbench. You should have been re-routed to the login page. On the the last step.
STEP 8: Throw in an admin scaffold for good measure
You have already build the foundations for a secured web app and could stop here. But this next step is just so easy it’s almost silly and it is useful to have a basic admin in place right from the get-go.
Go to the console and generate an admin controller:
rubyuser@linux:~/workspace/AgendaRedux> script/generate controller Admin
exists app/controllers/
exists app/helpers/
create app/views/admin
exists test/functional/
create app/controllers/admin_controller.rb
create test/functional/admin_controller_test.rb
create app/helpers/admin_helper.rb
Now in eclipse, open SecuredApp/app/controllers/admin_controller.rb and edit as follows:
class AdminController < ApplicationController
before_filter :isAdmin
scaffold :user
end
That’s it, your done. Test out the app by going to http://0.0.0.0:3000/account/login first and login in as ‘admin’ (make sure you create a user admin
). Then enter the url: http://0.0.0.0:3000/account/admin. You should see a very basic admin application.
And that concludes today’s lesson!
35 Comments Add your own
1. GettingStartedWithRails i&hellip | May 8th, 2006 at 5:58 pm
2. Sonjaya Tandon » HO&hellip | May 21st, 2006 at 2:45 pm
[…] Hit OK and you will find yourself in an eclipse perspective specifically designed for Rails. Now that you have your environment set-up, you are ready for your first application tutorial […]
3. [Rails] User Registration&hellip | June 21st, 2006 at 4:36 am
4. Ma.gnolia Blog&hellip | June 27th, 2006 at 10:01 pm
5. West Coast Logic&hellip | June 29th, 2006 at 10:46 pm
6. pj | August 4th, 2006 at 9:27 am
A few gotchas from a newbie in using this tutorial:
Make sure if you’re cutting and pasting the code to a text editor in Windows, make sure you replace all the single and double quotes manually. If not you will get an error something along the lines of “Unrecognized character \223″
Also, if you’re getting an error on the registration page because of the form_for tag, you need to update Rails. In your command window:
gem install rails –include-dependencies
Hopefully I saved you some time with these Gotchas.
7. sonjaya | August 4th, 2006 at 1:50 pm
Thanks pj for the feedback.
I need to reformat this article — when I do, I will encorporate your comments.
8. Ruby on Rails News&hellip | August 5th, 2006 at 10:31 pm
9. pj | August 6th, 2006 at 9:45 pm
One last tutorial fix:
Once you have created an ‘admin’ account, you will want to ignore the article’s address of http://0.0.0.0:3000/account/admin and instead go to:
http://0.0.0.0:3000/admin
That will give you the basic admin interface based on the scaffold.
-PJ
10. 6-clicks.com&hellip | August 14th, 2006 at 5:11 pm
11. GettingStartedWithRails i&hellip | August 23rd, 2006 at 2:17 am
12. fimion | August 23rd, 2006 at 5:32 pm
The way the registration stuff is set up (needing password confirmation and such) renders the admin scaffolding useless, seeing as how when you attempt to change a user’s details, you need to enter in a password and password confirmation (for which there are no fields). Wouldn’t it be wiser to put the password checks and such in a controller?
13. fimion | August 23rd, 2006 at 6:01 pm
Update to my last post:
besure to add a
n => :create to all of your validation checks.
14. Bookmarks&hellip | September 13th, 2006 at 3:25 pm
15. MarijnKoesen.nl » B&hellip | November 5th, 2006 at 6:16 am
[…] http://sonjayatandon.com/05-2006/how-to-build-a-secured-web-application-with-ruby-on-rails/ […]
16. Discover the best applica&hellip | November 29th, 2006 at 5:38 am
17. Rails Forum / Ruby on Rai&hellip | December 11th, 2006 at 3:21 am
18. GettingStartedWithRails (&hellip | December 25th, 2006 at 3:42 pm
19. Sonjaya Tandon » HO&hellip | April 18th, 2007 at 4:23 am
20. Radiant 0.6 Released : Ra&hellip | April 30th, 2007 at 6:01 am
21. 74 Quality Ruby on Rails &hellip | May 2nd, 2007 at 8:41 pm
22. Melhores sites sobre Ruby&hellip | May 3rd, 2007 at 6:42 pm
[…] How-to: Build a secured web application with Ruby on Rails […]
23. The Outboard Brain of a &hellip | May 13th, 2007 at 8:38 pm
24. indiehead | May 25th, 2007 at 1:54 am
nice work, I tried building myself a little login screen myself using digest/sha2 security last night, went well.
…but need a register screen, learning as i go though.
good work, thanks for posting this, will help a lot!.
John.
25. alassiter | May 28th, 2007 at 8:29 am
Newbie questions: How does this differ from using the acts_as_authenticated plugin?
Thanks. Anthony.
26. rails.loglibrary.com&hellip | June 11th, 2007 at 12:49 pm
27. moisty70 | June 23rd, 2007 at 2:45 am
Please help with this error:
compile error
./script/../config/../app/views/account/register.rhtml:5: syntax error, unexpected ‘)’
_erbout.concat “\t\t”; _erbout.concat(( form_for :user do |form| ).to_s); _erbout.concat “\n”
^
./script/../config/../app/views/account/register.rhtml:26: syntax error, unexpected kEND, expecting ‘)’
28. namrata | June 27th, 2007 at 3:24 am
I get the following error when I login for the fiest time. I cannot see the textfield next to the label too.
127.0.0.1 - - [27/Jun/2007:15:45:00 Pacific Standard Time] “GET /stylesheets/[enter%20your%20style%20sheets%20here].css HTTP/1.1″ 400 341
http://localhost:3001/account/login -> /stylesheets/[enter%20your%20style%20sheets%20here].css
29. DotMana » » R&hellip | July 10th, 2007 at 2:08 am
[…] How-to: Build a secured web application with Ruby on Rails – One of the most basic requirements with Rails is to have a secure Web application. Here is a breakdown for creating that secure environment. […]
30. mobiledurant | July 17th, 2007 at 5:58 pm
Here is the text for the rhtml files in STEP 5 and STEP 6, so people can copy and
paste.
SecuredApp/app/views/layouts/account.rhtml
<html>
<head>
<title><%= controller.action_name %></title>
<%= stylesheet_link_tag 'FIXME,
stylesheets', 'FIXME, eg. secured_app' %>
</head>
<body>
<p style="color: green"><%= flash[:notice] %></p>
<%= @content_for_layout %>
</body>
</html>
SecuredApp/app/views/account/login.rhtml
<div class="redux-form">
<fieldset>
<legend>Please Log In</legend>
<%= start_form_tag %>
<p>
<label
for="name">Name:</label>
<%= text_field_tag :username,
params[:username] %>
</p>
<p>
<label
for="password">Password:</label>
<%= password_field_tag :password,
params[:password] %>
</p>
<p>
<%= submit_tag "Login" %>
</p>
<%= end_form_tag %>
</fieldset>
<br>
<%= link_to 'Register', :action => :register %>
</div>
SecuredApp/app/views/account/register.rhtml
<div class="redux-form">
<%= error_messages_for 'user' %>
<fieldset>
<legend>Enter User Details</legend>
<% form_for :user do |form| %>
<p>
<label for="user_name"
style="width:30">Name:</label>
<%= form.text_field :username, :size
=> 40 %>
</p>
<p>
<label
for="user_password">Password:</label>
<%= form.password_field :password,
:size => 40 %>
</p>
<p>
<label
for="user_password_confirmation">Confirm:</label>
<%= form.password_field
:password_confirmation, :size => 40 %>
</p>
<%= submit_tag "Add User", :class => "submit"
%>
<% end %>
</fieldset>
</div>
SecuredApp/app/views/layouts/workbench.rhtml
<html>
<head>
<title><%= controller.action_name %></title>
<%= stylesheet_link_tag
'scaffold', 'redux' %>
</head>
<body>
<p style="color: green"><%= flash[:notice] %></p>
<%= @content_for_layout %>
</body>
</html>
SecuredApp/app/views/workbench/index.rhtml
<html>
<head>
<title><%= controller.action_name %></title>
<%= stylesheet_link_tag
'scaffold', 'redux' %>
</head>
<body>
<p style="color: green"><%= flash[:notice] %></p>
<%= @content_for_layout %>
</body>
</html>
31. RatePoint: - Ratings for &hellip | August 4th, 2007 at 7:45 pm
32. StumbleUpon » Your p&hellip | August 9th, 2007 at 9:44 am
33. » What is Ruby on R&hellip | October 12th, 2007 at 12:27 am
[…] Sonjaya Tandon » HOW-TO: Build a secured web application with Ruby on Rails - Perhaps will have better chance of success if we take one step at a time. Sonjaya Tandon » HOW-TO: Build a secured web application with Ruby on Rails […]
34. Discover From Your Favori&hellip | January 6th, 2008 at 3:40 pm
35. The New Way To Scaffold i&hellip | March 12th, 2008 at 9:00 am
Leave a Comment
You must be logged in to post a comment.
Trackback this post | Subscribe to the comments via RSS Feed