Devise Auth cho ứng dụng Rails sử dụng JSON Webservice

Chúng ta vẫn dùng thường dùng Rails để tạo ra các ứng dụng web và khi muốn tạo một ứng dụng mobile-friendly thì có hai con đường có thể chọn: hoặc là thiết kế một giao diện responsive hoặc là sẽ phát triển một ứng dụng cho các thiết bị di dộng. Để tạo ra một ứng dụng di động chúng ta phải tạo ra một giao diện để có thể kết nối tới server của ứng dụng Rails, đó là khi Web services phát huy tác dụng. Trong bài viết này, chúng ta sẽ thử xây dựng một JSON Web service cho ứng dụng Rails

Mục đích của chúng ta là xây dựng một API cho module xác thực người dùng sử dụng một gem rất quen thuộc là Devise. Tuy nhiên vì lý do bảo mật mà trong build mới nhất Devise đã bỏ đi tính năng token authentication.

Nhưng token authentication là gì? Có chức năng gì mà lại bị Devise bỏ đi?

Hiểu một cách đơn giản, authentication_token giống như một thẻ nhân viên, khi bạn đi vào công ty (sign in) thì bạn cần phải có, đi đến mỗi phòng ban khác nhau bảo vệ sẽ nhìn thẻ và biết được bạn có quyền đi vào không.

Khi người dùng send một request lên server thì sẽ đính kèm với authentication_token. Có hai cách để sử dụng token, thêm vào query string hoặc qua HTTP header.

Token authentication có vẻ rất quan trọng, vậy phải làm sao?

Nắng đã có mũ, mưa đã có ô, lạnh cảm cúm đã có tiffi, chưa có token auth đã có gem simple_token_authentication

Dạo đầu đủ rồi, bây giờ chúng ta sẽ bắt đầu thực hiện:

Tạo Rails app

rails new webservice_demo

Add gem và cài đặt

gem "devise"
gem "simple_token_authentication"
bundle install

Cài đặt model sử dụng Devise Authenticate

rails generate devise:install
rails generate devise User

Disable CRFS cho JSON request

application_controller.rb

protect_from_forgery with: :null_session

Tạo route cho API

Rails.application.routes.draw do
  devise_for :users
    namespace :api do
    namespace :v1 do
      devise_scope :user do
        post "sign_up", :to => 'registrations#create'
        post "sign_in", :to => 'sessions#create'
        delete "sign_out", :to => 'sessions#destroy'
      end
    end
  end
end

Tạo controller cho API

Trong thư mục app/controllers/api/v1 tạo các file:

  • applications_controller.rb (Controller của route "api/v1/")
class Api::V1::ApplicationController < ActionController::API
  acts_as_token_authentication_handler_for User, {fallback: :none}

  respond_to :json

  private
  def current_user
    @current_user ||= User.find_by authentication_token: request.headers["Authorization"] #token thông qua header
  end

  def authenticate_user_from_token
    render json: {message: "You are not authenticated"},
      status: 401 if current_user.nil?
  end
  end

  def ensure_params_exist
   return unless params[:user].blank?
    render json: {message: "Missing params"}, status: 422
  end

  def load_user_authentication
    @user = User.find_by_email user_params[:email]
    return login_invalid unless @user
  end

  def login_invalid
    render json:
      {message: "Invalid login"}, status: 200
  end
end
  • registrations_controller.rb (Controller của route "api/v1/sign_up")
class Api::V1::RegistrationsController < Devise::RegistrationsController
  before_action :ensure_params_exist

  respond_to :json

  def create
    user = User.new user_params
    if user.save
      render json: {message: "Registration has been completed",user: user}, status: 200
    else
      warden.custom_failure!
      render json: {message: error_messages(user.errors.messages), status: 200
    end
  end

  private
  def user_params
    params.require(:user).permit :email, :password, :password_confirmation
  end
end
  • sessions_controller.rb (Controller của route "api/v1/sign_in")
class Api::V1::SessionsController < Devise::SessionsController
  before_action :ensure_params_exist, only: [:create, :destroy]
  before_action :load_user_authentication

  respond_to :json

  def create
    if @user.valid_password? user_params[:password]
      sign_in @user, store: false
      render json: {message: "Signed in successfully",
        user: @user, status: 200
      return
    end
    invalid_login_attempt
  end

  def destroy
    if @user.authentication_token == user_params[:authentication_token] #token thông qua query string
      sign_out @user
      render json: {message: "Signed out"}, status: 200
    else
      render json: {message: "Invalid token"}, status: 200
    end
  end

  private
  def user_params
    params.require(:user).permit :email, :password, :authentication_token
  end

  def invalid_login_attempt
    render json: {message: "Sign in failed"}, status: 200
  end
end

Sau khi viết xong phần code cho server, bạn hãy sử dụng POSTMAN để tạo các request tới server.

  • Đăng ký: gửi request POST tới url /api/v1/sign_up với params
{
  "email": "[email protected]",
  "password": "12345678",
  "password_confirmation": "12345678"
}
  • Đăng nhập: gửi request POST tới url /api/v1/sign_in với params
{
  "email": "[email protected]",
  "password": "12345678"
}
  • Đăng xuất: gửi request DELETE tới url /api/v1/sign_out với params
{
  "authentication_token": "token của user"
}

Vậy là chúng ta đã tạo xong một API để thực hiện việc đăng ký, đăng nhập cho ứng dụng Rails server thông qua Devise. Bài viết đầu tiên chắc chắn sẽ còn nhiều thiếu xót, mong mọi người góp ý, bổ sung.

Thân ái và quyết thắng