Projekat

Općenito

Profil

Akcije

Podrška #14275

Zatvoren

rails authentication, authorization

Dodano od Ernad Husremović prije oko 17 godina. Izmjenjeno prije više od 16 godina.

Status:
Zatvoreno
Prioritet:
Normalan
Odgovorna osoba:
Početak:
14.05.2008
Završetak:
% završeno:

0%

Procjena vremena:


Povezani tiketi 1 (0 otvoreno1 zatvoren)

korelira sa rails - Podrška #15596: rails ssl client certificate authenticationZatvorenoErnad Husremović18.10.2008

Akcije
Akcije #1

Izmjenjeno od Ernad Husremović prije oko 17 godina

root@stor-34:/data/git/rails-authorization-plugin# git fetch -v

From git://github.com/DocSavage/rails-authorization-plugin
 = [up to date]      master     -> origin/master

root@stor-34:/data/git/restful_authentication# git-svn info

Path: .
URL: http://svn.techno-weenie.net/projects/plugins/restful_authentication
Repository Root: http://svn.techno-weenie.net/projects
Repository UUID: 567b1171-46fb-0310-a4c9-b4bef9110e78
Revision: 3176
Node Kind: directory
Schedule: normal
Last Changed Author: technoweenie
Last Changed Rev: 3176
Last Changed Date: 2008-03-23 21:28:19 +0100 (Ned, 23 Mar 2008)

root@stor-34:/data/git/acts_as_state_machine/trunk# git-svn info

Path: .
URL: http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk
Repository Root: http://elitists.textdriven.com/svn/plugins
Repository UUID: a9f67fa3-8408-0410-ad84-b6cefbfd10ac
Revision: 66
Node Kind: directory
Schedule: normal
Last Changed Author: sbarron
Last Changed Rev: 66
Last Changed Date: 2006-11-13 14:47:36 +0100 (Pon, 13 Nov 2006)

Akcije #2

Izmjenjeno od Ernad Husremović prije oko 17 godina

  • Naslov promijenjeno iz rails authentication u rails authentication, authorization
Akcije #3

Izmjenjeno od Ernad Husremović prije oko 17 godina

User authentication in Ruby on Rails

dobar tutorial za učenje rails-a općenito

Akcije #4

Izmjenjeno od Ernad Husremović prije oko 17 godina

User authentication in Ruby on Rails

Author: Kacper Cieśla (comboy)

This tutorial was written for Ruby on Rails noobs. If you already know something about rails, go straight to the model declaration.

So, first of all, you must be aware that there is a lot of plugins that implement authentication for your rails app. Some of them are:

  • LoginGenerator
  • ActAsAuthenticated
  • AuthGenerator

And there are many more . Some of them looks more like whole application templates rather than just plugins.

Plugins are good and there is no point in writing something that somebody has already wrote, but user authentication isn’t that hard thing to implement. That’s why I prefere to write it myself rather than running through pages of documentation, reading somebody’s dirty code and trying to understand it’s meaning to finally rewrite most of it anyway.
Let’s do it

Let’s start from the scratch:

$ rails userapp

create
create app/controllers
create app/helpers
create app/models
(...)

And in our newly created directory userapp edit file config/database.yml, I’ll use MySQL:

development:
adapter: mysql
database: test
username: foo
password: pass123
host: localhost

Preparing the user model

We use generator to create user model:

$ ruby 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

And we edit db/migrate/001_create_users.rb, When you’re done it should look like this:

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, :string
t.column :email, :string
t.column :created_at, :datetime
end
end

def self.down
drop_table :users
end
end

Of course columns email and created_at are optional (btw, created_at is a magic field)

And we run the migration:

$ rake db:migrate
CreateUsers: migrating ===================================================
-- create_table(:users)
-> 0.1500s
CreateUsers: migrated (0.1500s) ==========================================

Now it’s time to pimp up our model. First of all, if we set password for an user, it should be saved to columns password_salt and password_hash.

What are those? password_salt is just a random string, and password_hash is a md5 hash of password+password_salt (salt makes it harder to crack).

def password=(pw) # we generate a random string
salt = [Array.new(6){rand(256).chr}.join].pack("m").chomp # 2^48 kombinacji # and save salt and the hash
self.password_salt, self.password_hash =
salt, Digest::MD5.hexdigest(pw + salt)
end

Some nice function to check if given password is correct:

def password_is?(pw)
Digest::MD5.hexdigest(pw + password_salt) == password_hash
end

And to make it even better we add some validations:

validates_presence_of :username
validates_uniqueness_of :username

So finally, file app/models/user.rb looks like this:

class User < ActiveRecord::Base

validates_presence_of :username
validates_uniqueness_of :username
validates_confirmation_of :password
attr_reader :password
def password=(pw)
@password = pw # used by confirmation validator
salt = [Array.new(6){rand(256).chr}.join].pack("m").chomp # 2^48 combos
self.password_salt, self.password_hash =
salt, Digest::MD5.hexdigest(pw + salt)
end
def password_is?(pw)
Digest::MD5.hexdigest(pw + password_salt) == password_hash
end

end

New user registration

This is an example of creating simple rails app based on the given user model, so if you’re exprienced in rails, you can propably skip this and next paragraph.

We need some controller:

$ ruby script/generate controller main
exists app/controllers/
exists app/helpers/
create app/views/main
exists test/functional/s
create app/controllers/main_controller.rb
create test/functional/main_controller_test.rb
create app/helpers/main_helper.rb

Now we create app/views/main/register.rhtml with something like this:

<div align="right" style="width: 600px">
<%= '<b>'+flash[:info]+'</b><hr />' if flash[:info] >
<h3 align="center">New user registration</h3>
<
= error_messages_for 'user' >
<
form_tag(:action => 'register') do >
Login:
<
= text_field 'user', 'username' %>
<br /><br />

Password:
<%= password_field 'user', 'password' %>
&lt;br /&gt;&lt;br /&gt;
Password again:
<%= password_field 'user', 'password_confirmation' %>
&lt;br /&gt;&lt;br /&gt;
E-mail:
<%= text_field 'user', 'email' %>
&lt;br /&gt;&lt;br /&gt;
<%= submit_tag "Register me" >
<
end %>
&lt;/div&gt;

And related action in the controller file app/controllers/main_controller.rb:

class MainController < ApplicationController
def register
if request.post?
@user = User.new params[:user]
if @user.save
flash[:info] = 'You are registered now'
end
end
end
end

And it’s done. Easy, right?

After registration user will stay on the registration page so you may want to add some redirection. At the moment we don’t have a good place to redirect to, if we would have it’s enough to add:

redirect_to :action => 'index', :controller => 'main'

Let’s see if it really works:

$ ruby script/server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
(...)

And under our favourite browser we open http://localhost:3000/main/register

It does :)
Log in

To go further we need some page that will be availible only for logged in users. In this example I assume that we have some panel controller which is user control panel and should be availible only for registered users. Let’s generate it:

$ ruby script/generate controller panel
exists app/controllers/
exists app/helpers/
create app/views/panel
exists test/functional/
create app/controllers/panel_controller.rb
create test/functional/panel_controller_test.rb
create app/helpers/panel_helper.rb

Some tiny form for logging in (app/views/main/login.rhtml):

<%= '<font color="red">'+@auth_error+'</font><hr />' if @auth_error >
<
form_tag(:action => 'login') do >
Login:
<
= text_field_tag :login, params[:login] >
<br /><br />
Password:
<
= password_field_tag :password, params[:password] >
<br /><br />
<
= submit_tag "log in" >
<
end %>

(I haven’t say it will be impressive ;) ). And related action in the main controller:

def login
if request.post?
@user = User.find_by_username(params[:login])
if @user and @user.password_is? params[:password]
session[:uid] = @user.id
redirect_to :controller => 'panel', :action => 'secret'
else
@auth_error = 'Wrong username or password'
end
end
end

Tadaaaaa…

One detail: Sucessful login ends up with an error… That’s because we don’t have secret action in controller panel.
Checking if user is logged in

User was logged in with this line:

session[:uid] = @user.id

So when we have session[:uid] set then we know user is in. If control panel should be accessible only for registered users, we need to check if session[:uid] is not nil before every action.

There is an elegant way of doing this, and that is before_filter. We need to define a method that check if user is logged in, then add it to the before_filter.

Some simple log out action may be useful too.

When we write it down, our app/controllers/panel_controller.rb looks like this:

class PanelController < ApplicationController

before_filter :check_auth
def secret
render :text => 'This text is only for authenticated users'
end
def log_out
session[:uid] = nil
flash[:info] = 'You\'re logged out'
redirect_to :controller => 'main'
end
private
def check_auth
unless session[:uid]
flash[:error] = 'You need to be logged in to access this panel'
redirect_to :controller => 'main', :action => 'login'
end
end
end

Now try entering http://localhost:3000/panel/secret

I assume here that you have action index defined in your main controller, which also prints flash[:info] or flash[:error] – I believe you can prepare it by yourself

If you have some problems running it, here is a complete rails app that we’ve made already.
Remembering user

On pages that you visit often, there’s usually very nice option “remember me” when you’re logging in. Let’s try to implement it.

How to do it? We’re going to store a cookie on the client side, informing that given user is logged in. One cookie is enough, but in this implementation we’re going to use two:

  • first cookie is simply a login of the user
  • and the second one is the hash used to authenticate an user

Why some strange hash instead of just password? That’s because of security. I’ve made some assumptions about security:

  • reading cookie by somebody else should not reveal user’s password
  • even if somebody get our cookie, he should not be able to log in after we log out
  • and this cookie should not make it easier for him to guess our next cookie

We’ll need new column to store our cookie hash. Let’s generate a migration:

$ ruby script/generate migration AddUserCookieHash

It looks like this:

class AddUserCookieHash < ActiveRecord::Migration
def self.up
add_column :users, :cookie_hash, :string
end

def self.down
remove_column :users, :cookie_hash
end
end

We run it (rake db:migrate) and add in the logging in form:

<%= check_box_tag :remember %> remember me

Now we modify main controller. To be clear I paste whole log_in action:

def login
if request.post?
@user = User.find_by_username(params[:login])
if @user and @user.password_is? params[:password]
session[:uid] = @user.id
if params[:remember] # if user wants to be remembered
cookie_pass = [Array.new(9){rand(256).chr}.join].pack("m").chomp
cookie_hash = Digest::MD5.hexdigest(cookie_pass + @user.password_salt)
cookies[:userapp_login_pass] = { :value => cookie_pass, :expires => 30.days.from_now }
cookies[:userapp_login] = { :value => @user.username, :expires => 30.days.from_now }
User.update(@user.id, :cookie_hash => cookie_hash)
end
redirect_to :controller => 'panel', :action => 'secret'
else
@auth_error = 'Bad username or password'
end
end

So we’ve put some cookies in user’s browser wtih data that lets him authenticate. Now let’s write a code that will authenticate him if those cookies are present (and correct).

We want to log user in independetly of which controller he’ll be starting with. That’s why we’re gonna use our main controller (app/controllers/application.rb)

class ApplicationController < ActionController::Base # Pick a unique cookie name to distinguish our session data from others'
session :session_key => '_userapp_session_id'
before_filter :check_cookie

def check_cookie
return if session[:uid]
if cookies[:userapp_login]
@user = User.find_by_username(cookies[:userapp_login])
return unless @user
cookie_hash = Digest::MD5.hexdigest(cookies[:userapp_login_pass] + @user.password_salt)
if @user.cookie_hash == cookie_hash
flash[:info] = 'You\'ve been automatically logged in' # annoying msg
session[:uid] = @user.id
else
flash[:error] = 'Something is wrong with your cookie'
end
end
end

end

Now after restarting the browser user is still logged in.

to be continued (maybe)

Akcije #5

Izmjenjeno od Ernad Husremović prije više od 16 godina

  • Status promijenjeno iz Novo u Zatvoreno
Akcije

Također dostupno kao Atom PDF