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