Gem Devise - Rails

Khi bạn làm việc với ngôn ngữ lập trình yêu thích của mình, bạn thường tìm kiếm các công cụ để làm cho công việc của mình dễ dàng hơn. Trong Ruby, những công cụ này (gems) được tạo ra hàng ngày, nhưng chỉ một số trong số chúng là đủ tốt để chiếm được cảm tình của các coder. Gem devise là 1 trong những số đó. Nó được sử dụng rộng rãi hầu hết ở mọi project. Gem Devise là một giải pháp authentication linh hoạt cho Rails. Nó

  • Dựa trên Rack
  • Một solution MVC đầy đủ dựa trên Rails engines
  • Cho phép nhiều models cùng đăng nhập tại một thời điểm
  • Làm việc dựa trên module: Chỉ sử dụng những gì bạn thực sự cần. Nó chạy những module độc lập và riêng biệt

Cách hoạt động

Cài đặt trên Rails:

Giả sử ta muốn cài devise cho 1 model tên là User

echo "gem 'devise'" >> Gemfile    # Hoặc sửa Gemfile thêm dòng: gem 'devise'
bundle install                    # Cài đặt gem
rails generate devise:install     # Tạo file config và các file liên quan
rails generate devise user        # Tạo model class, routes ..
rake db:migrate                   # Tạo table user
rails generate devise:views users # Tạo view

Devise Configuration

Sau khi cài đặt gem devise,bước tiếp theo ta config được xác định dựa trên 2 file chính

  1. global file, config/initializers/devise.rb. Nếu có gì đó thay đổi trong file này, chúng ta cần phải restart lại server.
  2. Trong model user nơi đã cài đặt. Có thể tùy chỉnh chức năng thêm

Router

Ở router ta khai báo:

devise_for :users

Sẽ tự động sinh ra các router cần thiết như login, logout, reset password, edit password

# Session routes for Authenticatable (default)
     new_user_session GET    /users/sign_in                    {controller:"devise/sessions", action:"new"}
         user_session POST   /users/sign_in                    {controller:"devise/sessions", action:"create"}
 destroy_user_session DELETE /users/sign_out                   {controller:"devise/sessions", action:"destroy"}

# Password routes for Recoverable, if User model has :recoverable configured
    new_user_password GET    /users/password/new(.:format)     {controller:"devise/passwords", action:"new"}
   edit_user_password GET    /users/password/edit(.:format)    {controller:"devise/passwords", action:"edit"}
        user_password PUT    /users/password(.:format)         {controller:"devise/passwords", action:"update"}
                      POST   /users/password(.:format)         {controller:"devise/passwords", action:"create"}

# Confirmation routes for Confirmable, if User model has :confirmable configured
new_user_confirmation GET    /users/confirmation/new(.:format) {controller:"devise/confirmations", action:"new"}
    user_confirmation GET    /users/confirmation(.:format)     {controller:"devise/confirmations", action:"show"}
                      POST   /users/confirmation(.:format)     {controller:"devise/confirmations", action:"create"}

Nếu có namspace ta đặt trong namespace:

namespace :publisher do
  devise_for :account
end

Đoạn mã trên sẽ sử dụng publisher/sessions_controller thay vì devise/sessions_controller. Scoping: Bạn có thể đặt devise_for trong một scope:

scope "/my" do
  devise_for :users
end

scope ":locale" do
  devise_for :users
end

Bạn có thể yêu cầu config default_url_options trong ApplicationController. tự động thêm locale vào đường link

class ApplicationController < ActionController::Base
  def self.default_url_options
    { locale: I18n.locale }
  end
end

Custom router Thay đổi tên đường dẫn nếu bạn k muốn để tên mặc định

devise_scope :user do
  get "/some/route" => "some_devise_controller"
end
devise_for :users

Devise Utility Methods

Các helper method hỗ trợ hay dùng và hữu ích nhất là:

  • authenticate_user! : authenticate_user! là method ở trong controller. đảm bảo người dùng đã đăng nhập. Hàm này được gọi thông qua before_filter. Ví dụ:
class EndUserBaseController < ApplicationController
  before_filter :authenticate_user!
end

Nếu như user chưa đăng nhập, như mặc dịnh nó sẽ bị redirect về trang login page. Nếu như user đăng nhập thành công, nó sẽ về trang root mà bạn đã cài đặt trong routes, Root routes là route mặc định được định nghĩa trong file config/route.rb

root to: 'tours#index'

Sử dụng authenticate_user! đảm bảo việc user đăng nhập nên ta hoàn toàn có thể sử dụng method current_user để truy cập database hay thao tác trên nó:

class SentMessagesController < EndUserBaseController
  before_filter :authenticate_user!

  def index
    @sent_messages = current_user.sent_messages.all
  end
end

Before_filter :authenticate_user! đảm bảo current_user sẽ không bao giờ bị nil

  • current_user: Trả về người dùng đang đăng nhập. Nó sẽ trả về nil khi người dùng chưa đăng nhập
  • user_signed_in?: Trả về True hoặc False. True nếu user đã đăng nhập, và ngược lại
  • sign_in(@user) , sign_out(@user) : Thực hiện login, logout user
  • user_session: Trả về dữ liệu người dùng login.

Các tính năng - Modules

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
          :recoverable, :rememberable, :trackable, :validatable,
          :confirmable, :lockable, :timeoutable
end
  • database_authenticatable: Đảm bảo mật khẩu được nhập chính xác và mã hóa chúng trước khi lưu vào CSDL
  • confirmable: Đảm bảo việc người dùng đăng kí tài khoản sẽ xác nhận tài khoản qua mail mà Devise gửi. Đây là 1 biện pháp để tránh tạo các tài khoản fake. Các option allow_unconfirmed_access_for: Thời gian bạn muốn cho phép người dùng truy cập vào tài khoản của họ trước khi xác nhận. Sau thời gian này, quyền truy cập của người dùng bị từ chối confirm_within: Thời gian có thể confirm. Bạn có thể sử dụng điều này để buộc người sử dụng phải xác nhận trong một khoảng thời gian nhất định. Xác nhận sẽ không tạo mã thông báo mới nếu yêu cầu xác nhận lặp lại. Sử dụng điều này để cho phép người dùng của bạn truy cập một số tính năng của ứng dụng mà không cần xác nhận tài khoản. reconfirmable: Yêu cầu bất kỳ thay đổi email nào đều được xác nhận (chính xác theo cách như xác nhận tài khoản ban đầu) sẽ được áp dụng. Ví dụ:
User.find(1).confirm       # returns true unless it's already confirmed
User.find(1).confirmed?    # true/false
User.find(1).send_confirmation_instructions # manually send instructions
  • recoverable: Xử lí quên mật khẩu reset_password_keys: Các phím bạn muốn sử dụng khi khôi phục lại mật khẩu cho một tài khoản reset_password_within: Khoảng thời gian trong đó mật khẩu phải được đặt lại hoặc token hết hạn. sign_in_after_reset_password: Có hoặc không đăng nhập người dùng tự động sau khi đặt lại mật khẩu.
  • registerable : Cho phép người dùng đăng kí và sau đó thay đổi thông tin đăng nhập
  • rememberable: Khi chọn Remember me trên form login. Dựa trên cookie giúp lưu mật khẩu kể từ lần đăng nhập sau
  • trackable: Lưu trữ thông tin đăng nhập (địa chỉ IP máy người dùng, thời gian đăng nhập, tổng số lần đăng nhập) Các thông tin được lưu vào các cột: sign_in_count: Tăng sau mỗi lần đăng nhập current_sign_in_at: Đánh dấu thời gian khi người dùng đăng nhập last_sign_in_at: Thời gian đăng nhập trước đó current_sign_in_ip: IP truy cập khi người dùng đăng nhập last_sign_in_ip: IP truy cập lần trước đăng nhập
  • validatable : Đảm bảo email, mật khẩu phù hợp với một định dạng cụ thể. Có các option tương ứng là email_regexp: Biểu thức chính quy để xác nhận tính hợp lệ của email, password_length: Xác định độ dài của mật khẩu.
  • lockable: Giới hạn số lần đăng nhập sai. Hạn chế truy cập tài khoản trong 1 khoảng thời gian và gửi email bao gồm link để mở khóa tài khoản. Có các options để tùy biến như: maximum_attempts: Số lần nhập sai chấp nhận trước khi khóa người dùng. lock_strategy: khóa người dùng bằng :failed_attempts hoăc :none unlock_strategy: Bỏ khóa tài khoản bằng cách dùng :time, :email, :both hoặc :none. unlock_in: thời gian khóa tài khoản sau khi bị khóa. Chỉ khả dụng khi unlock_strategy là :time hoặc :both. unlock_keys: key sử dụng khi khóa và mở khóa tài khoản. Để tìm hiểu rõ hơn về các option này các bạn có thể tham khảo ở đây

Xử lý nhiều người dùng trong Devise

Trong phần này, chúng ta sẽ xem xét một số cách trong đó một số trường hợp sử dụng phổ biến liên quan đến hạn chế truy cập có thể được xử lý bằng các method của Devise

  • Trường hợp 1: Đảm bảo tất cả các action trong các controller đều phải đăng nhập trước khi truy cập ngoại trừ trang login và đăng kí
class ApplicationController < ActionController::Base
  # Ensures all actions invoke this (except those just below)
  before_filer :authenticate_user!
end

class AuthenticationController < ApplicationController
  # Turn off user authentication for all actions in this controller
  skip_before_filer :authenticate_user!

  def login
    '...'
  end

  def register
    '...'
  end
end
  • Trường hợp 2: Tất cả các action đều phải đăng nhập trước khi truy cập trừ 1 số action. Ví dụ :index, :show
class CrudController < ApplicationController
  before_filer :authenticate_user!, except: [ :index, :show ]
end

class BlogsController < CrudController
end

class CommentsController < CrudController
end
  • Trường hợp 3: Giả sử chúng ta muốn phân quyền truy cập cho người dùng,. Admin và user, cả 2 đều được đại diện bởi model riêng biệt. Một model User và Admin.
# All administrator controllers should inherit from this controller
class AdminController < ApplicationController
  before_filer :authenticate_admin!
end

# All end-user controllers should inherit from this controller
class EndUserController < ApplicationController
  before_filer :authenticate_user!
end
  • Trường hợp 4: Giả sử chúng ta có người dùng và admin đều chia sẻ chung một model. Tuy nhiên trong trường hợp này admin được xác định bởi 1 flag lưu trong model có giá trị true, false.
class ApplicationController < ActionController::Base
  before_filer :authenticate_user!
end

# All administrative controllers should inherit from this controller
class AdminController < ApplicationController
  before_filter :ensure_admin!

  private

  def ensure_admin!
    unless current_user.admin?
      sign_out current_user

      redirect_to root_path

      return false
    end
  end
end

Testing

Bạn có thể tạo ra 1 devise user bằng cách

User.create!(email: "[email protected]", password: "Aa12345")

Một ví dụ rspec

describe BlogController do
  let :user do
    User.create!(email: "[email protected]", password: "watching the telly")
  end

  before { sign_in user }

  context "creating a post" do
    post :create, subject: 'matter', body: 'and soul'

    expect(assigns(:blog)).to be_instance_of Blog

    expect(:blog.try :subject).to eq 'matter'

    expect(:blog.try :user).to eq user
  end
end

Hi vọng bài viết có thể giúp ích cho bạn!

Nguồn tham khảo :

http://www.rubydoc.info/github/plataformatec/devise/master/Devise/Models

https://launchschool.com/blog/how-to-use-devise-in-rails-for-authentication