Topic: HowTo - Advanced MD5 database authentication from scratch in 20 steps.
HowTo - Advanced MD5 database authentication from scratch in 20 steps.
This is an advanced md5 database authentication from scratch in 20 Steps Tutorial. is not meant to be the replacement of any of the Authentication plugins. The only purpose is to ilustrate
an easy way to put security into your rails application.
This is the advanced Tutorial based on:
[ Very simple authentication from scratch in 15 steps ] That is the reason many of the steps will look pretty similar ;-)
http://railsforum.com/viewtopic.php?id=15130
1.- Create your Rails Application
rails mylogin -d mysql
2.- Change to your new application directory.
cd mylogin
3.- Edit the config/database.yml file with your own settings.
Tip For windows users only. Open a windows explorer in your current path just like textmate ;-)
explorer /e, /root, c:\railapps\mylogin
Tip for Linux users. Check on customize gedit to look just like textmate ;-)
http://blog.ubrio.us/gnome/making-gedit
for-linux/
Tip for Mac users... never mind they don't need tips LOL
Anyway setup your config/database.yml file with your own db settings.
4.- Create your databases.
Run 1 of the following rake tasks to create the database(s) of your application.
If you want to create all the database use the rake task:
rake db:create:all
if you just want to create the development database for now just run:
rake db:create
5.- Once you create your database(s). Lets run some scaffold generators to make our life easier....
This is a simple Project list application with a Users table which are allowed to authenticate to the system, the sessions controllers is just to let us keep track of the
authentication.
ruby script\generate scaffold Project title:string body:text
ruby script\generate scaffold User username:string password:string
ruby script\generate controller sessions login logout
6.- Ok let the games begin... lets run our migrations. and create our tables into the DB.
rake db:migrate
7.- Lets Launch the web server and add a couple projects & a couple users...
$ruby script\server
Open your browser in
http://127.0.0.1:3000/projects.
Use the web interface to add 2 Projects or via console if you like it better.:
Project 1
Project 2
Lets add 2 users:
http://127.0.0.1:3000/users
User: foo Password bar
User:admin Password admin
I know what you have in mind... don't worry we will change those plain text passwords into MD5 hash. This is just to make it easier to see in this tutorial !!!.
8.- So far It's all pretty basic Rails stuff. Let's spicey up our Tutorial with the "User.authenticate(username,password)"
Lets add the database authentication function:: User.authenticate(username,password)
Edit the User Model located In the [ app/models/user.rb ] :
class User < ActiveRecord::Base
def self.authenticate(login, pass)
return false if login.nil? or pass.nil?
find_by_username_and_password(login,pass)
end
end
This is a method which can be accessible from the console when you call:
User.authenticate("foo","bar") => Then looks to the Users table and if it founds a username called "foo" with password "bar" returns the record, otherwise returns nil.
>ruby script\console
Loading development environment (Rails 2.0.2)
>> User.authenticate("foo","bar")
=> #<User id: 1, username: "foo", password: "bar", created_at: "2008-04-14 17:26:53", updated_at: "2008-04-14 17:26:53">
9.- Let's implement "Virtual Attributes" for the User Model: [ app/models/user.rb ]
Ryan Bates did a great job on: [ http://railscasts.com/episodes/16 ]
Lets add the getter & setter for mypassword:
def mypassword
password.to_s
enddef mypassword=(pass)
self.password= MD5.new(pass).to_s
end
Since we going to use MD5 we must declare it as required with:
require 'MD5'
The final script looks like this:
require 'MD5'
class User < ActiveRecord::Base# validates_length_of :username, :within => 5..40
# validates_length_of :password, :within => 5..40
# validates_presence_of :username
# validates_uniqueness_of :usernamedef mypassword
password.to_s
enddef mypassword=(pass)
self.password= MD5.new(pass).to_s
enddef self.authenticate(login, pass)
return false if login.nil? or pass.nil?
find_by_username_and_password(login,pass)
endend
At this point is required to restart the Web server to take the changes of the require 'MD5' of the User Model.
10.- Lets delete the public/index.html page
rm public/index.html
11.- Edit the config/routes.rb with a default route, resources for the sessions, home_path, login and logout !!!
ActionController::Routing::Routes.draw do |map|
map.resources :users
map.resources :projects
map.resources :sessionsmap.home '', :controller => 'projects', :action => 'index'
map.login 'login', :controller => 'sessions', :action => 'new'
map.logout 'logout', :controller => 'sessions', :action => 'destroy'
...
12.- Let's secure the Index Projects page for the New Project: [ app/views/projects/index.html.erb ]
<h1>Listing projects</h1><table>
<tr>
<th>Title</th>
<th>Body</th>
</tr><%= link_to 'New project', new_project_path %>
<% for project in @projects %>
<tr>
<td><%=h project.title %></td>
<td><%=h project.body %></td>
<% if admin? %>
<td><%= link_to 'Show', project %></td>
<td><%= link_to 'Edit', edit_project_path(project) %></td>
<td><%= link_to 'Destroy', project, :confirm => 'Are you sure?', :method => :delete %></td>
<% end %>
</tr>
<% end %>
</table><br />
<% if admin? %>
<%= link_to 'New project', new_project_path %>
<% end %>
The ifadmin? method will allow to see and access the administrative links only if the admin? method returns true.
At this point your web server will show undefined method `admin?'. but It's ok we are about to define it.
13.- Lets declare this important admin? method in the Application Controller to make it accessible for all the models.
[ app/controller/application.rb ]
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
Lets add the protected methods...
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the timehelper_method :admin?
protected
def authorize
unless admin?
flash[:error] = "unauthorized access"
redirect_to home_path
false
end
end
def admin?
username = session[:username]
password = session[:password]if User.authenticate(username, password)
true
else
false
endend
Now if you refresh your projects page, will se the Projects without the administrative links. But still accessible via http://127.0.0.1:3000/projects/show/1 until we apply our
before_filter.
14.- In the projects controller file add a before_filter method to perform the authorize validation previously declared
[ app/controller/projects_controller.rb ]
class ProjectsController < ApplicationController
# GET /projects
# GET /projects.xml
....
Replace with:
class ProjectsController < ApplicationController
before_filter :authorize, :except => :index# GET /projects
# GET /projects.xml
....
Now try to access the http://127.0.0.1:3000/projects/show/1 And will be redirected to http://127.0.0.1:3000/projects Pretty Cool Huh !!!.
15.- Lets create 2 methods on the sessions controller to perform the login authentication:
[ app/controller/sessions_controller.rb ]
class SessionsController < ApplicationControllerdef login
enddef logout
end
end
Replace with:
class SessionsController < ApplicationControllerdef create
session[:username] = params[:username]
session[:password] = MD5.new(params[:password]).to_s# flash[:notice] = "Successfully logged in"
redirect_to home_path
enddef destroy
reset_session
flash[:notice] = "Successfully logged out"
redirect_to login_path
end# def login
# end# def logout
# endend
16.- I'ts time to create the Login page. Rename the file called login.html.erb to new.html.eb.
$mv app/views/sessions/login.html.erb app/views/sessions/new.html.erb
17.- Working with the Login/Logout Views
Edit this app/views/sessions/new.html.erb:
<h1>Sessions#login</h1>
<p>Find me in app/views/sessions/login.html.erb</p
Replace with:
<center>
<% form_tag sessions_path do %>
<p>
Username <%= text_field_tag :username %>
Password <%= password_field_tag :password %>
<%= submit_tag "Login" %>
</p>
<% end %>
</center>
Now for the Logout: Edit the app/views/sessions/logout.html.erb
<h1>Sessions#logout</h1>
<p>Find me in app/views/sessions/logout.html.erb</p
Replace with:
<center>
<h3> Successfully Logout </h3>
</center>
18.- Create an application layout in [ app/views/layouts/application.rb ] This will let us in a very easy way to put a very simple navigation for your application.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>My Rails SSO: <%= controller.action_name %></title>
<%= stylesheet_link_tag 'scaffold' %>
</head>
<body><p style="color: green"><%= flash[:notice] %></p>
<hr size="1" color="#000000">
<%= yield %>
<hr size="1" color="#000000">
<% if admin? %>
<%= "Logged as: " + session[:username] %> <br>
<%= link_to "Logout", :controller => 'sessions', :action => 'destroy' %>
<% else %>
<%= "Not Logged in: " %> <br>
<%= link_to "Login", new_session_path %>
<% end %>
<hr size="1" color="#000000">
<center>
[ <%= link_to "Users", :controller => 'users', :action => 'index' %> ]  
[ <%= link_to "Projects", :controller => 'projects', :action => 'index' %> ]  </center>
<hr size="1" color="#000000">
</body>
</html>
=> To make it active delete or rename the projects & users layouts. You can refresh your web applications and see the application layout navigation working.
19.- Update the User views for New & Edit pages:
[ app/views/users/new.html.erb ] && [ app/views/users/edit.html.erb ]
Change the name of the :password to :mypassword => This will use the Virtual Attribute declared in the user model.
<p>
<b>Password</b><br />
<%= f.text_field :mypassword %>
</p>
This change will activate the feature of create MD5 everytime a user/password is created or edited.
Open your browser at http://127.0.0.1:3000/users and create a couple more users... see the MD5 Hash ;-)
Try with the previously created users once you update the current values the application will update will update with the hash of the password.
20.- That's all folks, try to login with one of the users you have in your users table.
You should be able to click on the login link in your application and login with some of your users.
Once you logged into your application you should be able to see the links to administrer the Projects table. The navigation menu will show you as which user you are
logged in.
In case you logout from the application. The Projects page will show only the list of Projects without administrative links. All the administrative links will not be accessible at
this point ![]()
CONCLUSION:
User.authenticate give us a big range of choices to authenticate users from our Rails Application. In this case from a table in a Database, but could it be from another
Single Sign On Server, Web Services or something else.
Enjoy to learn a little bit about more rails authentication, because I like do things from scratch, then when I learn can start using more advanced plugins ;-)
The more I learn about Ruby & Rails the more I want to use it for everything ![]()
TODO:
There is a little bug in the [ Login/Logout ] Link Navigation. The properly link will show but if you edit a User or a Project the link Logout
[ <%= link_to "Logout", :controller => 'sessions', :action => 'destroy' %> ] will point to:
[ http://127.0.0.1:3000/sessions/1 ] instead of [ http://127.0.0.1:3000/logout ]
In case you click on this logout link:
Unknown action
No action responded to show
I have a couple ideas on how to fix this... the easiest solution & ugly solution will be to hardcode the link to Logout !!!.
Alternavite ugly link to logout in [ app/views/layouts/application.rb ]
[ <a href="http://127.0.0.1:3000/logout" >Logout</a>]  
<%= link_to "Logout", :controller => 'sessions', :action => 'destroy' %>
But I still like to keep it secure, so the next idea is to do something with the routes, but I guess that will be subject for another post.
Din00z
Brains R Like Books only work when they R Open.