Rails Authentication With Warden(Without Devise)
Bài đăng này đã không được cập nhật trong 6 năm
Khi nói đến xác thực trong Rails ta hay nghĩ đến gem Devise:
Devise is a flexible authentication solution for Rails based on Warden
Devise là một giải pháp authentication linh hoạt cho Rails dựa trên Warden
Nhưng đôi khi những hỗ trợ của Devise sẽ k cần thiết do yêu cầu thay đổi, Devise không phải là một sự lựa chọn tối ưu nữa. Như dự án mình làm không thể sử dụng Devise được do spec thay đổi. Vậy trong trường hợp này ta lên làm gì để phù hợp, tối ưu?? Để giải quyết vấn đề này thì ta có thể làm việc trực tiếp ở tầng sâu hơn đó là Warden
Warden thực hiện xác thực ở middleware level. Nó thực hiện bằng cách sử dụng Strategies
Các tính năng của Devise: Trước khi lọai bỏ Devise ra khỏi ứng dụng của bạn. chúng ta cùng xem những gì sẽ phải làm để thay thế các support của Devise:
- Một vài Strategy có tính năng giống với
DatabaseAuthenticatable, Rememberable
... - Controller filters và helpers như:
authenticate, user_signed_in?, current_user, user_session
...
Cài đặt
Ta bắt đầu với ứng dụng rails-api hiển thị thời gian:
rails g controller Welcome index
Thêm vào routes:
# config/routes.rb
Rails.application.routes.draw do
get 'welcome/index'.
root 'welcome#index'
end
# app/controllers/welcome_controller.rb
class WelcomeController < ApplicationController
def index
render text: "Welcome guest, it's #{Time.now}"
end
Sau đó ta add gem warden
và bcrypt
(Để sử dụng has_secure_password
mã hóa password)
# Gemfile
gem 'warden'
gem 'bcrypt'
User Model
Ta tạo 1 model User chứa authentication_token
, password_digest
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :username
t.string :authentication_token
t.string :password_digest
t.timestamps null: false
t.index :authentication_token, unique: true
end
end
end
Ở model user.rb
:
# app/models/user.rb
class User < ActiveRecord::Base
after_create :generate_authentication_token!
has_secure_password
private
# Generate a session token
def generate_authentication_token!
self.authentication_token = Digest::SHA1.hexdigest("#{Time.now}-#{self.id}-#{self.updated_at}")
self.save
end
end
Add Warden to the Rack Middleware stack
Chúng ta cần phải thêm Warden vào middleware stack của ứng dụng
Nhìn vào middleware stack hiện tại:
$ rake middleware
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000052979d0>
use Rack::Runtime
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run RailsApiWarden::Application.routes
Như ở phần hướng dẫn cài đặt Warden có nhắc đến: Warden must be downstream of some kind of session middleware..
Chúng ta sẽ thêm Warden sau ActionDispatch::ParamsParser
# config/application.rb
# Add Warden in the middleware stack
config.middleware.insert_after ActionDispatch::ParamsParser, Warden::Manager do |manager|
end
Sau đó kiểm tra xem Warden đã thực sự trong stack:
$ rake middleware
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000037c80f0>
use Rack::Runtime
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::ParamsParser
use Warden::Manager
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run RailsApiWarden::Application.routes
Như vậy là đã thành công!
Strategy – Authentication Token
Ta sẽ định nghĩa 1 strategy để authenticate user thông qua params authentication_token
# lib/strategies/authentication_token_strategy.rb
class AuthenticationTokenStrategy < ::Warden::Strategies::Base
def valid?
authentication_token
end
def authenticate!
user = User.find_by_authentication_token(authentication_token)
user.nil? ? fail!('strategies.authentication_token.failed') : success!(user)
end
private
def authentication_token
params['authentication_token']
end
end
authenticate!
sẽ tìm kiếm user
mà match với authentication_token
Chúng ta có thể nhìn thấy method authenticate!
gọi fail!
hoặc success!
. Đây là các method helper được cung cấp bởi Warden.
Strategy
đã được cài đặt, Ta thêm nó vào Warden
bằng cách:
# config/application.rb
# Add Warden in the middleware stack
config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
manager.default_strategies :authentication_token
end
#config/initializers/warden.rb
require Rails.root.join('lib/strategies/authentication_token_strategy')
Warden::Strategies.add(:authentication_token, AuthenticationTokenStrategy)
Controller
Ta sẽ add thêm các method giống với các helper của Devise cung cấp như: current_user
, signed_in?
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include WardenHelper
end
# app/controllers/concerns/warden_helper.rb
module WardenHelper
extend ActiveSupport::Concern
included do
helper_method :warden, :signed_in?, :current_user
prepend_before_filter :authenticate!
end
def signed_in?
!current_user.nil?
end
def current_user
warden.user
end
def warden
request.env['warden']
end
def authenticate!
warden.authenticate!
end
end
Như vậy đã xong, giờ ta test thử:
Trường hợp authenticate thành công:
Trường hợp authenticate thất bại: Nhập bừa 1 authenticate_token và sẽ thấy 1 trang thông báo khá xấu
Ta có thể thay thế trang lỗi bằng cách thêm failure_app
:
# config/application.rb
config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
manager.default_strategies :authentication_token
manager.failure_app = UnauthorizedController
end
# app/controllers/unauthorized_controller.rb
class UnauthorizedController < ActionController::Metal
def self.call(env)
@respond ||= action(:respond)
@respond.call(env)
end
def respond
self.response_body = "Unauthorized Action"
self.status = :unauthorized
end
Như vậy đã hoàn thành. Hi vọng bài viết sẽ giúp ích cho bạn trong tương lai
Nguồn Tham Khảo:
http://blog.maestrano.com/rails-api-authentication-with-warden-without-devise/
All rights reserved