Series Hướng Dẫn Lập Trình Ruby on Rails (Phần 9) Hướng dẫn xây dựng ứng dụng API đơn giản với gem doorkeeper
Bài đăng này đã không được cập nhật trong 6 năm
Chào các bạn,
Giới thiệu
Hôm nay mình sẽ tiếp tục Series Hướng Dẫn Lập Trình Ruby on Rails, trong bài này mình sẽ hướng dẫn các bạn cách xây dựng một ứng dụng API đơn giản. Mình sẽ tiếp tục làm trên project đã có sẵn từ trước đến nay đó là cái Login App của chúng ta. Bạn này quên hoặc chưa có thì có thể download nó lại tại đây
Công cụ
Trong bài này để xây dựng một ứng dụng API mình sẽ sử dụng
- Gem doorkeeper : để generate access token.
- Gem active_model_serializers: để hỗ trợ customize các attributes mà mình muốn trả về trong API
- Postman: công cụ để test API. Nếu các bạn chưa biết dùng POSTMAN thì các bạn có thể tham khảo một bài viết khá hay ở đây
OK. Như vậy phần chuẩn bị đã xong. Chúng ta bắt tay vào thực hiện nhé.
Tiến hành
Cài đặt gem Doorkeeper
Đầu tiên chúng ta cần add 2 cái gems trên vào Gemfile và chạy bundle
gem "doorkeeper"
sau đó chạy bundle install
Để cài đặt gem "doorkeeper"
tiếp theo chạy lệnh:
rails g doorkeeper:install
Sau khi chạy lệnh trên nó sẽ sinh ra cho chúng ta file /config/initializers/doorkeeper.rb
dùng để config các tính năng của gem như:
- Thời gian expires của access token:
access_token_expires_in
. Mặc định nếu không thay đổi sẽ là 2 hours - Sử dụng refresh access token:
use_refresh_token
- Config resource owner
- ...
Ngoài ra còn thêm nhiều phần khác các bạn có thể vào trang github của nó để đọc nhé :v
Trong phần này mình sẽ dùng chức năng refresh_token
nên sẽ enable
nó lên
Để generate file migration mặc định của doorkeeper chạy:
rails g doorkeeper:migration
sau khi generate thì nó sinh ra cho chúng ta cái file migration như này:
Tuy nhiên chúng ra cần chỉnh sửa lại một chút :v
Vì trong bài viết này chúng ta cần sinh ra token cho những user trong model User đã được đăng ký từ Web LoginApp từ trước.
Do đó ta sẽ có cấu trúc quan hệ ở đây là one user has_many oauth_access_tokens
Mở file migration và chỉnh sửa lại phần add_foreign_key như bên dưới:
.
.
.
add_index :oauth_access_tokens, :token, unique: true
add_index :oauth_access_tokens, :resource_owner_id
add_index :oauth_access_tokens, :refresh_token, unique: true
add_foreign_key(
:oauth_access_tokens,
:users,
column: :resource_owner_id
)
sau đó chạy
rake db:migrate
Nó sẽ sinh ra cho chúng ta 3 cái table:
- oauth_access_tokens
- oauth_access_grants
- oauth_applications
Đối với lần hướng dẫn này, chúng ta chỉ cần lưu ý đến table oauth_access_tokens
vì nó sẽ chứa thông tin cần dùng để authenticate như: token, expires_in, revoked_at,...
Thêm cái relationship has_many access_token vào model user
class User < ApplicationRecord
has_secure_password
has_many :access_tokens, class_name: "Doorkeeper::AccessToken",
foreign_key: :resource_owner_id, dependent: :destroy
end
ok như vậy cơ bản là xong phần cài đặt.
Viết API
Trong bài này chúng ta sẽ xây dựng 3 cái API:
- User login để lấy access token
- User logout: revoke access token
- Get list users hiện tại
application_controller.rb
Để project chúng ta được clear: chúng ta sẽ đặt các api vào trong namespace là api
- Tạo thư mục
api
bên trong thư mụccontrollers
- Tạo một file
application_controller.rb
dùng cho các api khác kế thừa, bản thân nó sẽ kế thừa từ lớpActionController::API
mà rails đã cung cấp sẵn cho chúng ta.
application_controller.rb
class Api::ApplicationController < ActionController::API
before_action :doorkeeper_authorize!
before_action :current_user
def doorkeeper_unauthorized_render_options error
{json: {errors: I18n.t("doorkeeper.errors.messages.unauthorized_client", uid: current_user&.id)}}
end
def current_user
@current_user ||= User.find_by(id: doorkeeper_token.resource_owner_id) if doorkeeper_token
end
end
:doorkeeper_authorize!
là một method trong helper mà Doorkeeper cung cấp sẵn cho
chúng ta dùng để authenticate người dùng khi họ gửi token lên
sessions_controller.rb
Controller này sẽ chứa 2 api: login và logout
ta sẽ chọn action #create cho việc thực hiện login và action #destroy cho việc thực hiện logout của user
Trong API login: user cần phải gửi name và password lên để chúng ta trả về access token cho user ấy. Trong API logout: user chỉ cần gửi access token mà đã nhận được lên để chúng ta thực hiện hủy giá trị của token ấy đi là xong.
Việc lưu trữ access token sẽ được phía Client thực hiện.
Ta sẽ có file sessions_controller.rb
như thế này
class Api::SessionsController < Api::ApplicationController
skip_before_action :doorkeeper_authorize!, only: :create
def create
user = User.authenticate users_params
if user
render json: {data: Api::GenerateTokenService.new(user).call}, status: 200
else
render json: {errors: I18n.t("errors.api.sessions_controller.login.failed")}, status: 400
end
end
def destroy
revoke_token_service = Api::RevokeTokenService.new doorkeeper_token.token
revoke_token_service.execute
if revoke_token_service.success?
render json: {}, status: 200
else
render json: {errors: revoke_token_service.errors}, status: 400
end
end
private
def users_params
params.require(:user).permit :name, :password
end
end
Đầu tiên là thằng
user = User.authenticate users_params
dùng dể chứng thực name
và password mà user gửi lên có đúng hay không.
đã có thông tin input và output vào model user viết nó nào:
user.rb
class User < ApplicationRecord
.
.
.
class << self
def authenticate params
user = User.find_by name: params[:name]
return false unless user
user.authenticate params[:password]
end
end
end
Ta sẽ có hàm authenticate
như này, đơn giản đúng không.
Tiếp theo:
Api::GenerateTokenService.new(user).call
Service này dùng để gen cho chúng ta 1 cái access token sau khi user đã được chứng thực thành công.
trong thư mục app
các bạn tạo thêm thư mục services/api
, sau đó chúng ta sẽ tạo 1
class Api::GenerateTokenService
dùng để gen access token như bên dưới
class Api::GenerateTokenService
attr_reader :user
def initialize user
@user = user
end
def call
generate_token user
end
private
def generate_token user
access_token = Doorkeeper::AccessToken.create! resource_owner_id: user.id,
expires_in: Doorkeeper.configuration.access_token_expires_in, use_refresh_token: true
token_info = Doorkeeper::OAuth::TokenResponse.new(access_token).body
.merge uid: user.id, name: user.name
created_at = token_info["created_at"]
token_info["created_at"] = Time.zone.at(created_at).iso8601
token_info.merge expires_on: Time.zone.at(created_at + token_info["expires_in"]).iso8601
end
end
Như vậy là đã xong.
Tiếp theo:
Api::RevokeTokenService.new doorkeeper_token.token
Service RevokeTokenService
dùng để hủy một cái access token khi user thực hiện logout,
hoặc sau này các bạn cho thể tận dụng để thực hiện hủy cái access token ở một chỗ nào khác
nếu muốn :v.
Ta sẽ có như sau:
class Api::RevokeTokenService
attr_reader :token, :errors
def initialize token
@token = token
end
def execute
revoke_token token
end
def success?
errors.nil?
end
private
def revoke_token token
access_token = Doorkeeper::AccessToken.find_by token: token
if access_token
access_token.revoke
else
@errors = I18n.t "doorkeeper.errors.messages.invalid_token.unknown"
end
end
end
Phần viết Controller và Service như vậy là đã ổn, giờ chỉ việc vào routes cấu hình và test thử nữa là ok.
Thêm vào file routes.rb
như bên dưới:
Rails.application.routes.draw do
use_doorkeeper do
skip_controllers :applications, :authorized_applications
end
namespace :api do
post "login" => "sessions#create"
delete "logout" => "sessions#destroy"
end
get "login" => "sessions#new"
post "login" => "sessions#create"
delete "logout" => "sessions#destroy"
resources :users
end
Như vậy là ok rồi
Mở rails s
và test thử thành quả nào :v
API: api/login
API: api/logout
users_controller.rb
Bây giở chúng ta sẽ viết API để get list users hiện tại nhé.
Để get list users chúng ta sẽ sử dụng action #index
của controller này.
Chúng ta sẽ có như sau:
class Api::UsersController < Api::ApplicationController
before_action :users, only: :index
def index
render json: @users, status: 200
end
private
def users
@users = User.all
end
end
Thêm vào routes.rb
namespace :api do
.
.
.
resources :users
end
OK chúng ta sẽ gọi thử API get list users nhé.
Để gọi API này chúng ta cần phải truyển lên access token mà hồi nãy sau khi thực hiện gọi
api login nhận được:
ví dụ: mình có access token là 8f2d221b119878907e14d7face59ef5a108813069c410b3305a67a5fb7ab1c3c
Thì header khi gọi API list users sẽ như sau
Authorization
: Bearer 8f2d221b119878907e14d7face59ef5a108813069c410b3305a67a5fb7ab1c3c
Content-Type
: json
Ngon chạy được rồi đấy =]]
Tuy nhiên ở đây chúng ta thấy có field password_digest
là field mà chúng ta không mong muốn trả về vì đây là chuỗi mã hóa mật khẩu của user.
Vậy chúng ta cần loại nó ra khỏi response trả về.
Vì mặc định rails nó sẽ generate tất cả attributes đang có trong model thành json rồi trả về. Do đó để customize được các attributes chúng ta sẽ sử dụng gem active_model_serializers
Cài đặt gem active_model_serializers
Đưa cái này vào Gemfile
rồi chạy bundle install
gem "active_model_serializers", "~> 0.10.0"
Để customize được các attributes cần trả về của model User chúng ta cần tạo 1 class serializer của model User.
Bằng cách:
rails g serializer user
gem active_model_serializers
sẽ tự động generate cho chúng ta class UserSerializer
class UserSerializer < ActiveModel::Serializer
attributes :id
end
Chúng ta sẽ có như trên. Giờ muốn response về attributes nào của model user chỉ cần điền vào phần attributes ở trên là được.
Mình sẽ customize nó lại như sau:
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :created_at
end
Mở rails s
và chạy lại xem nào
OK bây giờ nó chỉ response về những attributes mà mình muốn.
Ngoài ra thằng active_model_serializers
còn cho phép chúng ta thực hiện generate theo relationship và nhiều cái khác nữa. Các bạn chịu khó tìm hiểu thêm trên trang Github của nó nhé .
Link full source code: https://github.com/duc11t3bk/login_app
OK. Hôm nay mình tạm dừng tại đây. Hẹn các bạn vào kỳ tới nhé :v
All rights reserved