12/08/2018, 13:57

Gem Warden

1.Rack middleware Trong rails nói đến authenticate mọi người thường nghĩ ngay đến "Gem devise" một công cụ đắc lực vô cùng tuyệt vời cho chức năng này. Nhưng hôm nay, tôi sẽ giới thiệu đến các bạn "Gem warden", một dependency của devise. Để hiểu về warden, chúng ta sẽ bắt đầu với Rack ...

1.Rack middleware

Trong rails nói đến authenticate mọi người thường nghĩ ngay đến "Gem devise" một công cụ đắc lực vô cùng tuyệt vời cho chức năng này. Nhưng hôm nay, tôi sẽ giới thiệu đến các bạn "Gem warden", một dependency của devise. Để hiểu về warden, chúng ta sẽ bắt đầu với Rack middleware.

  • Rack cung cấp một giao diện tối thiểu, mô-đun, và khả năng thích nghi để phát triển các ứng dụng web trong Ruby.
  • Ngày nay, ruby on rails, sinatra và một số ngôn ngữ khác đều dùng rack làm mặc định để nói chuyện với servers.

-Middleware:

  • Nằm giữa server và framework. Rack có thể tùy chỉnh để ứng dụng của bạn sử dụng middleware. Ví dụ:
  • Rack::URLMap : để định hướng cho nhiều ứng dụng hoạt động trong cùng một quá trình.
  • Rack::ShowException : để bắt các ngoại lệ...

-Để hiển thị ra các middleware trong rails, bạn gõ câu lệnh:

rake middeware

use Rack::Sendfile
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001f0dab0>
use Rack::Runtime
use Rack::MethodOverride
......

-Để thêm mới một middleware trong rails, bạn gõ lệnh

config.middleware.use(new_middleware)
config.middleware.insert_after(existing_middleware, new_middleware)
config.middleware.insert_before(existing_middleware, new_middleware)

-Để đổi vị trí giữa 2 middleware

config.middleware.swap(existing_middleware, new_middleware)

-Xóa middleware:

config.middleware.delete existing_middleware

2.Warden

a. Giới thiệu

Warden là một rack based middleware, nó cung cấp một cơ chế cho việc authenticate trên các ứng dụng web ruby. Warden rất linh hoạt, nó cung cấp một cơ chế cho phép authentication trong bất kỳ 1 ứng dụng rack based nào.

b. Tại sao lại dùng warden

Hiện nay việc tạo nhiều ứng dụng cùng chạy trên một process đang thu hút rất nhiều người. Với mỗi ứng dụng sẽ yêu cầu một cơ chế authentication, nhưng với warden thì vấn đề này sẽ được giải quyết.

c. Một số đặc điểm về warden

Warden dùng strategies để authenticate nhưng nó lại không hề tạo ra cho bạn bất kì một strategies nào mà thay vào đó bạn phải tự tạo và tự code. Warden cũng không cung cấp cho chúng ta bất kì 1 view, 1 controller hay một helper nào cả.

Dependencies: Rack

*Strategies

Warden sử dụng khái niệm strategies để xác định nếu có request authenticate. Kết quả trả về sẽ là một trong 3 trạng thái sau:

  • One succeeds
  • No strategies are found relevant
  • A strategy fails

Strategies là nơi để bạn đặt code logic cho một authenciate request.

Là lớp kế thừa từ Warden::Strategies::Base

*Callbacks

Warden cung cấp một số hàm callbacks sau:

  • after_set_user
  • after_authentication
  • after_fetch
  • before_failure
  • after_failed_fetch
  • before_logout
  • on_request

*Failures

Khi một failure sảy ra trong qua trình xác thực authenticate, một throw[:warden] sẽ được ném ra.

[123, 132] in /home/mr/.rvm/gems/ruby-2.3.0/gems/warden-1.2.6/lib/warden/proxy.rb
   123:     #   env['warden'].authenticate!(:password, :scope => :publisher) # throws if it cannot authenticate
   124:     #
   125:     # :api: public
   126:     def authenticate!(*args)
   127:       user, opts = _perform_authentication(*args)
=> 128:       throw(:warden, opts) unless user
   129:       user
   130:     end
   131:
   132:     # Check to see if there is an authenticated user for the given scope.
(byebug)

*Một số features khác

  • Authenticate session data
  • Scopes

*Một chút so sánh về devise và warden

  • Dependencies: Warden: Rack, Devise: Warden
  • Strategies mặc định: Warden: 0, Devise: 4
  • Failure_app mặc định: Warden: không, Devise: có
  • Views, controllers, helpers mặc định: Warden: không, Devise: có
  • Độ linh hoạt khi có sự thay đổi: Warden: Xử lý đơn giản, Devise: Khá phức tạp.

3. Bắt đầu quá trình thực thi

Để bắt đầu bạn add gem warden vào gemfile và bundle để cài đặt.

gem "warden"

Bạn cần một model User để lưu thông tin đăng nhập

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :email
      t.string :password
      t.string :authentication_token

      t.timestamps null: false
    end
  end
end

Add warden vào Rack middleware

config/application.rb

config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
end

Vì thứ tự các rake middleware rất quan trọng, nó ảnh hưởng đến các tính năng được thực hiện ở các rack tiếp theo. Thường thì ta sẽ add warden sau ActionDispatch::Session::CookieStore, nhưng để sử dụng được Flash, chúng ta nên add sau ActionDispatch::Flash

Sau đó tạo 1 file strategy và code logic tại đây

lib/strategies/password_strategy.rb

class PasswordStrategy < ::Warden::Strategies::Base
  def valid?
    return false if request.get?
    user_data = params.fetch("session", {})
    (user_data["email"]&&user_data["password"]).present? || authentication_token.present?
  end

  def authenticate!
    if authentication_token
      user = User.find_by_authentication_token authentication_token
      user.nil? ? fail!() : success!(user)
    else
      user = User.find_by_email params["session"]["email"]
      if user.nil? || user.password != params["session"]["password"]
        fail!()
      else
        success! user
      end
    end
  end

  private
  def authentication_token
    params["authentication_token"]
  end
end

Bây giờ chúng ta cần add strategies vào warden

config/application.rb

config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
  manager.default_strategies :password_strategy
    # Nếu có thêm strategy thì bạn chỉ việc thêm tên strategy vào sau dòng trên
    # ví dụ
    #  manager.default_strategies :password_strategy, :basic_auth
end
config/initialize/warden.rb

require Rails.root.join('lib/strategies/password_strategy')

Warden::Strategies.add(:password_strategy, PasswordStrategy)

Giờ chúng ta sẽ tạo 1 helper để thực thi các hàm current_user, logged_in?, authenticate!...

controllers/concern/warden_helper.rb

module WardenHelper
  extend ActiveSupport::Concern

  included do
    helper_method :warden, :logged_in?, :current_user

    prepend_before_action :authenticate!
  end

  def logged_in?
    current_user.present?
  end

  def current_user
    warden.user
  end

  def warden
    env['warden']
  end

  def authenticate!
    warden.authenticate! :password_strategy
  end
end

Tạo thêm 2 controller session và registration để đăng nhập và đăng ký

controllers/sessions_controller.rb

class SessionsController < ApplicationController
  skip_before_filter :authenticate!

  def create
    authenticate!
    redirect_to user_path(current_user)
  end

  def destroy
    warden.logout
    redirect_to new_sessions_path
  end
end
controllers/registrations_controller.rb

class RegistrationsController < ApplicationController
  skip_before_action :authenticate!

  def new
    @user = User.new
  end

  def create
    @user = User.new user_params
    if @user.save
      flash[:notice] = "Registrations User Success"
      redirect_to new_sessions_path
    end
  end

Chúng ta cần 1 controller UnauthoziedController đóng vai trò là failure app để báo lỗi khi xảy ra lỗi trong quá trình authenticate

class UnauthorizedController < ActionController::Metal
  include ActionController::UrlFor
  include ActionController::Redirecting
  include Rails.application.routes.url_helpers
  include Rails.application.routes.mounted_helpers

  delegate :flash, to: :request

  class << self
    def call env
      @respond ||= action :respond
      @respond.call env
    end
  end

  def respond
    unless request.get?
      env["warden.options"][:message] = "Logged in fail!"
      flash.alert = env["warden.options"][:message]
    end
    redirect_to new_sessions_url
  end
end

Khai báo cho warden biết là nó có một failure_app

config/application.rb

config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
  manager.default_strategies :password_strategy
  manager.failure_app = UnauthorizedController
end

Một số view khá đơn giản nên mình sẽ không show lên đây. Như vậy với một chút kiến thức mình đã chia sẻ hi vọng mọi người đã phần nào hiểu được về Warden. Một số link tham khảo:

https://github.com/hassox/warden/wiki/Overview http://pothibo.com/2013/07/authentication-with-warden-devise-less/ http://blog.maestrano.com/rails-api-authentication-with-warden-without-devise/ https://github.com/hassox/warden/blob/master/lib/warden/strategies/base.rb

0