12/08/2018, 18:02

Authentication with Warden, devise-less (part 2)

Ở bài trước, mình cũng đã từng giới thiệu việc authenticate thông quan warden. Hôm nay mình sẽ giới thiệu thêm những ứng dụng thực tiễn cho việc sử dụng warden để authentication trong trường hợp phức tạp hơn, thông qua một bảng trung gian để shared thông tin giữa các loại user khác nhau. Việc ...

Ở bài trước, mình cũng đã từng giới thiệu việc authenticate thông quan warden. Hôm nay mình sẽ giới thiệu thêm những ứng dụng thực tiễn cho việc sử dụng warden để authentication trong trường hợp phức tạp hơn, thông qua một bảng trung gian để shared thông tin giữa các loại user khác nhau. Việc authen này gần như đối với devise thì việc custom lại rất phức tạp. Nhưng đối với warden thì việc chúng ta hoàn toàn có thể xử lý dễ dàng hơn rất nhiều.

Bài toán ví dụ:

  • Chúng ta có 1 server và cần implement cho việc authentication thông qua 3 subdomain:
    • http://admin.foo.local được authentication bằng tài khoản admin
    • http://manager.foo.local được authentication bằng tài khoản manager
    • http://foo.local được authentication bằng tài khoản user

Mỗi tài khoản được đăng kí và sử dụng cho những domain khác nhau. Thông thường dùng devise chúng ta hoàn toàn có thể authentication thông qua 3 model khác nhau (Admin, Manager, User) và điều đó rất dễ dàng để thực thi. Nhưng đối với yêu cầu của bài toán là: Trên domain admin, chúng ta đã đăng kí với 1 email là nguyen.thi.ngoc@framgia.com với password: Aa@123456, bên manager và user nếu có đăng kí bằng email nguyen.thi.ngoc@framgia.com thì cũng sẽ đăng nhập được với password tương tự admin và khi admin reset password, thì bên manager, user cũng sẽ đăng nhập được bằng với password mới đó.

Cách giải quyết.

Ở đây chúng ta có rất nhiều cách giải quyết, ví dụ:

  • Lưu email, password vào 3 model và khi có tài khoản nào đó thay đổi email, password thì chúng ta cũng sẽ gọi callback vào 3 model còn lại một cách làm đơn giản (chúng ta vẫn có thể sử dụng devise) nhưng dữ liệu bị lưu lặp lại vào 3 model, và khách hàng thì không muốn xảy ra việc này.
  • Cách thứ 2: Lưu thông tin login chung ra 1 bảng gọi là bảng Account với fields: email, encrypted_password (những field common chung khác nữa) và Admin, Manager, User sẽ chứa account_id vậy khi 1 trong 3 tài khoản admin, manager, user mà thay đổi email hay password thì cả 3 tài khoản đều đăng nhập được với thông tin mới thay đổi. Tuy nhiên với trường hợp này có vẻ devise là bất lực (sẽ phải sửa hầu gần như rất rất nhiều thứ) => Sự lựa chọn hợp lý bây giờ chúng là warden

Ở bài trước thì mình có giới thiệu cách authentication đơn giản với model user (có email và encrypted_password), ở bài này chúng ta sẽ extend thêm việc authentication thông qua 1 model thứ 3 bằng việc sử dụng Scopes để authentication cho 3 subdomain trên.

Đầu tiên ta cần định nghĩa thêm Strategies dùng cho việc authentication Admin gọi là admin_login_strategy.rb Trong này ta check điều kiện để admin có thể authenticate vào hệ thống và hoàn toàn có thể control nó một cách chủ động. Ở đây chúng ta sẽ authen bằng tài khoản admin thông quan việc check account có tồn tại không? admin có tài khoản nào liên kết với account đó không và cuối cùng là check passwod nhập vào đã đúng chưa, nếu tất cả thông tin đều đúng thì chúng ta sẽ authetication với admin đó, còn không sẽ báo lỗi.

class AdminLoginStrategy < ::Warden::Strategies::Base
  def valid?
    email || password
  end

  def authenticate!
    account = Account.find_by_email email
    admin = account&.admin
    if user && admin && account.password == password
      success! admin
    else
      fail!("Could not log in")
    end
  end

  private
  def email
    params["session"].try :[], "email"
  end

  def password
    params["session"].try :[], "password"
  end
end

Trong config/initializers/warden.rb chúng ta thêm Strategies vừa mới khai báo

require Rails.root.join('lib/strategies/admin_login_strategy')
Warden::Strategies.add(:admin_login, AdminLoginStrategy)

Tiếp đến chúng ta cần khai báo thêm session và default_strategies, khai báo scope_defaults mới để quản lý scope admin. Trong config/application.rb chúng ta thêm:

Warden::Manager.serialize_into_session(:admin) { |a| a.id }
Warden::Manager.serialize_from_session(:admin) { |id| Admin.find_by_id(id) }

manager.default_strategies :admin_login
manager.scope_defaults :admin, strategies:[:admin_login]

Tiếp theo là định nghĩa thêm một số helper như là current_admin, authenticate_admin! trong warden_helper có tính năng tương tự devise

def current_admin
    warden.user(:admin)
end

def admin_authenticate!
    warden.authenticate! :admin_login, scope: :admin
end

Cuối cùng trong UnauthorizedController chúng ta cần định nghĩa lại, đối với những domain khác nhau, thì chúng ta cần redirect về chính xác màn login mà mình mong muốn

case request.subdomain
when /^admin/
    redirect_to admin_login_path
when /^manager/
    redirect_to manager_login_path  
 else
    redirect_to new_sessions_path
end

Mọi bước setup đã hoàn thành giờ chúng ta chỉ cần dùng, tương tự như với devise. Muốn authentication trước khi access vào 1 màn hình nào đó chúng ta chỉ cần sử dụng

before_action :admin_authenticate!

Thật dễ dàng đúng không. Và để logout khỏi hệ thống chúng ta có nhiều lựa chọn.

warden.logout  # Clear the session.  Logs everyone out
warden.logout(:default) # logout the :default user
warden.logout(:admin)  # logout the :admin user

Rất dễ dàng có thể kiểm soát việc login, logout khỏi hệ thống. Hy vọng bạn sẽ có nhiều lựa chọn hơn trong việc authentication.

Demo

https://github.com/nguyenngoc2505/warden-demo

0