Cách xây dụng một API đơn giản trong ứng dụng Rails của bạn <Part 2>
Như đã nói ở bài trước Ở đây mình đã giới thiệu các bước cơ bản để xây dựng API cho Ruby on Rails (RoR). Bài viết hôm nay sẽ giới thiệu tiếp về cách xây dụng API trong RoR. Bất cứ viết code cho một chương trình xử lý nào việc bảo mật đều vô cùng quan trọng. Chính vì vậy xây dựng API trong RoR ...
Như đã nói ở bài trước Ở đây mình đã giới thiệu các bước cơ bản để xây dựng API cho Ruby on Rails (RoR). Bài viết hôm nay sẽ giới thiệu tiếp về cách xây dụng API trong RoR.
Bất cứ viết code cho một chương trình xử lý nào việc bảo mật đều vô cùng quan trọng. Chính vì vậy xây dựng API trong RoR cũng vô cùng quan trọng và cần thiết.
Trong API bạn không thể sử dụng các tệp tin cookies và sessions. Thay vào đó, khi người dùng muốn đăng nhập và gửi yêu cầu bằng phương thức HTTP POST với tên truy cập và mật khẩu của mình đến API lúc này API sẽ gửi lại cho người dùng một token, token này sẽ xác định được người dùng ấy là ai, và người dùng đã được chứng thực.
Trong mỗi yêu cầu xác thực của người dùng yêu cầu API xác thực, API sẽ trả về một mã token để xác thực. Nếu tìm thấy người dùng thì xác thực trả về true và người dùng được xác thực thành công, ngược lại không tìm thấy người dùng nào thì API trả về lỗi 401, lỗi này thông báo cho người dùng biết việc xác thực của bạn đã thất bại.
Đầu tiên chúng ta xây dựng một hàm callback để tạo ra token khi người dùng gọi đến hàm này, token sẽ được tạo ra. Hàm callback được viết như sau:
before_create :generate_authentication_token def generate_authentication_token loop do self.authentication_token = SecureRandom.base64(64) break unless User.find_by(authentication_token: authentication_token) end end
Sau đó chúng ta sẽ thêm xử lý sessions.
class Api::V1::SessionsController < Api::V1::BaseController def create user = User.find_by(email: create_params[:email]) if user && user.authenticate(create_params[:password]) self.current_user = user render(json: Api::V1::SessionSerializer.new(user, root: false).to_json, status: 201 ) else return api_error(status: 401) end end private def create_params params.require(:user).permit(:email, :password) end end
Sau đó chúng ta sẽ serializer sessions:
class Api::V1::SessionSerializer < Api::V1::BaseSerializer attributes :id, :email, :name, :admin, :token def token object.authentication_token end end
Tiếp theo chúng ta sẽ xây dựng hàm xác thực người dùng, hàm được viết như sau:
def authenticate_user! token, options = ActionController::HttpAuthentication::Token.token_and_options(request) user_email = options.blank?? nil : options[:email] user = user_email && User.find_by(email: user_email) if user && ActiveSupport::SecurityUtils.secure_compare(user.authentication_token, token) @current_user = user else return unauthenticated! end end
ActionController::HttpAuthentication::Token sẽ phân tích request gửi lên bởi người dùng thành token và email, request sẽ được phân tích ra trông như code bên dưới:
Authorization: Token token="VCiPlgG9fbQHkpjzp4JnVcDm2KR5zpu39xY2lx6kkMYXhkvIkTRGSfLAeaQH1aDls548d05a4QS4uJTOIYJ3/g==", email="vo.van.do@framgia.com"
Lúc này current_user sẽ là biến lưu giá trị xác thực.
Authorization giống như là giấy phép cho mà người dùng có thể xác thực được. Nói cách khác người dùng muốn xác thực được thì API đưa ra các yêu cầu và người dùng phải thực hiện đúng các yêu cầu đó.
Chúng ta sẽ xây dụng giấy phép yêu cầu người dùng phải tuân thủ, hàm này được viết như sau:
class UserPolicy < ApplicationPolicy def show? return true end def create? return true end def update? return true if user.admin? return true if record.id == user.id end def destroy? return true if user.admin? return true if record.id == user.id end class Scope < ApplicationPolicy::Scope def resolve scope.all end end end
Trông nó rất đơn giản phải không các bạn. Tất nhiên rồi, đối với các lập trình viên đơn giản hiệu quả và tính bảo mật cao là vấn đề cốt lõi để giải quyết các vấn đề. Khi ta sử dụng API việc đơn giản hóa là rất quan trọng.
Phân trang là điều rất cần thiết với 1 API
- Giúp ta giảm thiểu số lượng bản ghi hiển thị
- Việc truy vấn giới hạn số lượng bản ghi giúp cho tốc độ load nhanh hơn.
def paginate resource resource = resource.page(params[:page] || 1) if params[:per_page] resource = resource.per_page(params[:per_page]) end return resource end #expects pagination! def meta_attributes(object) { current_page: object.current_page, next_page: object.next_page, prev_page: object.previous_page, total_pages: object.total_pages, total_count: object.total_entries } end
Chúng ta vào file config/application.rb để đặt giá trị cần phân trang.
config.middleware.use Rack::RedisThrottle::Daily, max: 10000
Chúng ta thêm middleware vào file config.rb
config.middleware.insert_before 0, "Rack::Cors" do allow do origins '*' resource '*', :headers => :any, :methods => [:get, :post, :put, :patch, :delete, :options, :head] end end
Như vậy bạn có thể truy cập các method từ bất cứ nơi nào bạn muốn gọi.
Để kiểm tra các method trong API viết chuẩn hay chưa. Chúng ta lên viết Spec để kiểm tra các trường hợp dữ liệu đầu vào, đầu ra trả về đúng yêu cầu hay chưa. Phần code phía dưới là ví dụ về các viết Spec cho API của bạn:
describe Api::V1::UsersController, type: :api context :show do before do create_and_sign_in_user @user = FactoryGirl.create(:user) get api_v1_user_path(@user.id), format: :json end it 'returns the correct status' do expect(last_response.status).to eql(200) end it 'returns the data in the body' do body = HashWithIndifferentAccess.new(MultiJson.load(last_response.body)) expect(body[:user][:name]).to eql(@user.name) expect(body[:user][:updated_at]).to eql(@user.updated_at.iso8601) end end end
Trong Spec tôi có viết hàm create_and_sign_in_user để kiểm tra dữ liệu trả về đúng hay chưa. Đầu vào là token và email, đầu ra trả về người dùng đó xác thực thành công.
module AuthenticationHelper def sign_in user header('Authorization', "Token token="#{user.authentication_token}", email="#{user.email}"") end def create_and_sign_in_user user = FactoryGirl.create(:user) sign_in(user) return user end alias_method :create_and_sign_in_another_user, :create_and_sign_in_user def create_and_sign_in_admin admin = FactoryGirl.create(:admin) sign_in(admin) return admin end end RSpec.configure do |config| config.include AuthenticationHelper, :type=>:api end
Vâng, vậy là qua 2 bài viết trên tôi đã hướng dẫn các bạn các bạn làm thế nào để xây dựng một API trong RoR là như thế nào. Mong rằng qua bài viết này sẽ giúp các bạn hiểu thế nào là xây dựng một API tốt cho các dự án của mình. Mọi ý kiến đóng góp cho bài viết, các bạn có thể để lại comment bên dưới. (thankyou)
-
Using Rails for API-only Applications
-
Ruby on Rails API documentation tool
-
Gem Kollegorna