Authlogic

#dev, #ruby

O Authlogic é uma gem para executar tarefas de autenticação com muitos recursos: criptografia de senhas, logout por inatividade, dentre outros.

Primeiro instale o Authlogic:

Depois configure o seu arquivo config/environment.rb conforme abaixo, na linha 11:

RAILS_GEM_VERSION = '2.3.3' unless defined? RAILS_GEM_VERSION

require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config|
  # Specify gems that this application depends on and have them installed with rake gems:install
  # config.gem "bj"
  # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
  # config.gem "sqlite3-ruby", :lib => "sqlite3"
  # config.gem "aws-s3", :lib => "aws/s3"
  config.gem "authlogic"

  config.time_zone = 'UTC'
end

Enxuguei o arquivo para que ficasse somente com o necessário e os exemplos de configurações de gems, que, como você pode ver, pode-se também configurar por versões.

Com a configuração feita vamos gerar todos os models, controllers e views necessárias.

Vamos primeiro criar:

$ script/generate session user_session
      exists  app/models/
      create  app/models/user_session.rb

Por fim o arquivo deve-se parecer com:

class UserSession < Authlogic::Session::Base
end

Vamos criar o controller:

$ ./script/generate controller user_sessions
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/user_sessions
      exists  test/functional/
      create  test/unit/helpers/
      create  app/controllers/user_sessions_controller.rb
      create  test/functional/user_sessions_controller_test.rb
      create  app/helpers/user_sessions_helper.rb
      create  test/unit/helpers/user_sessions_helper_test.rb

Adicione o conteúdo abaixo ao arquivo app/controllers/user_sessions_controller.rb, ele deve ter somente a primeira e última linha:

class UserSessionsController < ApplicationController
  skip_before_filter :require_user, :check_role

  def new
    @user_session = UserSession.new
  end

  def create
    @user_session = UserSession.new(params[:user_session])
    if @user_session.save
      flash[:notice] = nil
      redirect_back_or_default users_url
    else
      flash[:notice] = "Login failed"
      render :action => :new
    end
  end

  def destroy
    current_user_session.destroy
    redirect_back_or_default root_url
  end
end

E para completar ao app/controllers/application_controller.rb:

# Filters added to this controller apply to all controllers in the application.
# Likewise, all the methods added will be available for all controllers.

class ApplicationController < ActionController::Base
  before_filter :require_user

  filter_parameter_logging :password, :password_confirmation
  helper_method :current_user_session, :current_user

  private
  def current_user_session
    return @current_user_session if defined?(@current_user_session)
    @current_user_session = UserSession.find
  end

  def current_user
    return @current_user if defined?(@current_user)
    @current_user = current_user_session && current_user_session.user
  end

  def require_user
    unless current_user
      store_location
      redirect_to new_user_session_url
      return false
    end
  end

  def store_location
    session[:return_to] = request.request_uri
  end

  def redirect_back_or_default(default)
    redirect_to(session[:return_to] || default)
    session[:return_to] = nil
  end

  helper :all # include all helpers, all the time
  protect_from_forgery # See ActionController::RequestForgeryProtection for details

  # Scrub sensitive parameters from your log
  # filter_parameter_logging :password
end

Somente da linha 5 a 36 foi adicionado.

A única view necessária é a de login, vamos criar então o arquivo app/views/user_sessions/new.html.erb:

<div class="loginbox">
  <h2>Login</h2>
  <% form_for @user_session, :url => user_session_path do |f| %>

    <p><%= f.label :login %><br />
      <%= f.text_field :login, :size => 20 %></p>
    <p><%= f.label :password %><br />
      <%= f.password_field :password, :size => 20 %></p>

    <%= f.submit "logar" %>
  <% end %>
</div>

E por fim o gerenciamento dos usuários com o comando abaixo:

$ ./script/generate scaffold user login:string name:string email:string crypted_password:string password_salt:string persistence_token:string login_count:integer last_request_at:datetime last_login_at:datetime current_login_at:datetime last_login_ip:string current_login_ip:string active:boolean
$ ./script/generate scaffold user \
> login:string \
> name:string \
> email:string \
> crypted_password:string \
> password_salt:string \
> persistence_token:string \
> login_count:integer \
> last_request_at:datetime \
> last_login_at:datetime \
> current_login_at:datetime \
> last_login_ip:string \
> current_login_ip:string \
> active:boolean
      exists  app/models/
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/users
      exists  app/views/layouts/
      exists  test/functional/
      exists  test/unit/
      exists  test/unit/helpers/
      exists  public/stylesheets/
      create  app/views/users/index.html.erb
      create  app/views/users/show.html.erb
      create  app/views/users/new.html.erb
      create  app/views/users/edit.html.erb
      create  app/views/layouts/users.html.erb
      create  public/stylesheets/scaffold.css
      create  app/controllers/users_controller.rb
      create  test/functional/users_controller_test.rb
      create  app/helpers/users_helper.rb
      create  test/unit/helpers/users_helper_test.rb
       route  map.resources :users
  dependency  model
      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/20090724124349_create_users.rb

Vou traduzir de forma rápida a documentação de como ficaria a migração:

t.string    :login,               :null => false                # opcional, pode-se usar e-mail ou ambos
t.string    :crypted_password,    :null => false                # requerido
t.string    :password_salt,       :null => false                # opcional, mas extremamente recomendado
t.string    :persistence_token,   :null => false                # requerido
t.string    :single_access_token, :null => false                # opcional, veja Authlogic::Session::Params
t.string    :perishable_token,    :null => false                # opcional, veja Authlogic::Session::Perishability
t.integer   :login_count,         :null => false, :default => 0 # opcional, veja Authlogic::Session::MagicColumns
t.datetime  :last_request_at                                    # opcional, veja Authlogic::Session::MagicColumns
t.datetime  :current_login_at                                   # opcional, veja Authlogic::Session::MagicColumns
t.datetime  :last_login_at                                      # opcional, veja Authlogic::Session::MagicColumns
t.string    :current_login_ip                                   # opcional, veja Authlogic::Session::MagicColumns
t.string    :last_login_ip                                      # opcional, veja Authlogic::Session::MagicColumns

O que estivem como Authlogic::Session::MagicColumns são colunas “mágicas” que são alteradas conforme o uso.

Eu costumo manter o active:boolean pois se estiver como falso para um usuário este usuário não vai poder logar.

Para que seja possível o login o model de usuários deve conter um conteúdo semelhante a:

class User < ActiveRecord::Base
  acts_as_authentic do |auth|
    auth.logged_in_timeout = 60.minutes
  end
end

Após criar o scaffold do usuário, vamos editar as views de edição para solicitar apenas as informações como login, senha, confirmação de senha e ativo.

Arquivo app/views/users/new.html.erb:

<% form_for(@user) do |f| %>
  <%= f.error_messages %>

  <%= render :partial => "write", :locals => { :f => f } %>

  <p>
    <%= f.submit "Criar" %>
  </p>
<% end %>

Arquivo app/views/users/edit.html.erb de forma semelhante:

<% form_for(@user) do |f| %>
  <%= f.error_messages %>

  <%= render :partial => "write", :locals => { :f => f } %>

  <p>
    <%= f.submit "Atualizar" %>
  </p>
<% end %>

E por fim a partial app/views/users/_write.html.erb:

<p>
  <%= f.label :login %><br />
  <%= f.text_field :login %>
</p>
<p>
  <%= f.label :name %><br />
  <%= f.text_field :name %>
</p>
<p>
  <%= f.label :email %><br />
  <%= f.text_field :email %>
</p>
<p>
  <%= f.label :password %><br />
  <%= f.text_field :password %>
</p>
<p>
  <%= f.label :password_confirmation %><br />
  <%= f.text_field :password_confirmation %>
</p>
<p>
  <%= f.label :active %><br />
  <%= f.check_box :active %>
</p>

Veja que para criar e editar os usuários apenas estes campos são necessários, pois campos como login_count são modificados pelo authlogic de forma dinâmica.

Com as views de usuários pronta vamos migrar o banco e adicionar nosso primeiro usuário

$ rake db:migrate
(in /home/dmitry/Projects/Authlogic_example)
==  CreateUsers: migrating ====================================================
-- create_table(:users)
   -> 0.0045s
==  CreateUsers: migrated (0.0046s) ===========================================

E antes de iniciar o servidor para testar vamos adicionar as seguintes rotas:

ActionController::Routing::Routes.draw do |map|
  map.resources :users

  map.resource :user_session
  map.login '/login', :controller => 'user_sessions', :action => 'new'
  map.logout '/logout', :controller => 'user_sessions', :action => 'destroy'

  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
end

E criar o primeiro usuário.

$ ./script/console
Loading development environment (Rails 2.3.3)
>> User.create!(:login => "usuario", :name => "Primeiro Usuario", :email => "mail@valido.com", :password => "senha", :password_confirmation => "senha", :active => true)
=> #<User id: 1, login: "usuario", name: "Primeiro Usuario", email: "mail@valido.com", crypted_password: "0ab834ca00acc9ec3ff90d3c1fb83ff753a8b504f619c89d7f6...", password_salt: "qBCd39TzjTInLtl4sZsS", persistence_token: "816d2cb216602fd78dbae61dc4963b79e55a034fac32427bca7...", login_count: nil, last_request_at: nil, last_login_at: nil, current_login_at: nil, last_login_ip: nil, current_login_ip: nil, active: nil, created_at: "2009-07-24 12:55:58", updated_at: "2009-07-24 12:55:58">

Conforme fiz guardei o projeto e coloquei em http://github.com/dmitrynix/Authlogic_example.