+1

Token-based Authentication with Ruby on Rails 5 API

I. Giới thiệu

Các ứng dụng phát triển sử dụng API ngày cảng trở nên phổ biến thì các phương thức xác thực ưa chuộm nhất hiện nay là sử dụng token-based 👍👍👍. Trong hướng dẫn này, tôi sẽ cung cấp tóm lược tổng quan về xác thực dựa trên token-based và cách triển khai trong ứng dụng API trong Rails 5.

II. Thế nào là Token-based authentication?

Token-based authentication còn được biết dưới một cái tên khác(JON WEB TOKEN authentication) là một cách để xử lý xác thực người dùng trong các ứng dụng. Nó là một lựa chọn thay thế cho việc** session-base authentication** (Xác thực theo phiên làm việc)

  • Sự khác biệt đáng chú ý nhất của token authenticationsession authentication là session authentication bị phụ thuộc vào máy chủ. Một bản nghi sẽ được tạo chứa thông tin đăng nhập của người dùng. Toàn bộ thôn tin của đăng nhập sẽ được lưu trữ trên máy chủ. Còn token authentication không lưu bất cứ thứ gì trên máy chủ nhưng sẽ tạo ra những token duy nhất và sự dụng nó để kiểm tra mỗi khi có một yêu cầu dược gửi lên từ người dùng. Không giống sư session authentication, sử dụng token sẽ không liên kết người dùng với thông tin đang đăng nhập. Nhiều ứng dụng như Facebook, Google, Github đã tiếp cận với phương pháp này.

III. Những lợi ích khi sử dụng phương pháp này.

  • Cross-domain / CORS Cookie và CORS không bị lẫn lộn với nhau. Cách tiếp cận này cho phép bạn thực hiện gọi AJAX tới bất kì máy chủ nào bạn sử dụng HTTP header để truyền thông tin người dùng.

  • Stateless (nó là một thiết kế để giao tiếp giữa client và server và không lưu lại vết. Sau khi người dùng gửi yêu cầu thì server sử lý rồi trả về dữ liệu. Ngay sau đó thì kết nối này sẽ bị cắt đứt.) Làm giảm tải việc lưu trữ thông tin người dùng trên server.

  • Tách biệt Không bị ràng buộc với một chương trình xác thực cụ thể. Token có thể được tạo ở bất cứ đâu, vì thế API cũng có thể được gọi từ bất cứ đâu bằng một lệnh xác thực duy nhất thay vì nhiều lệnh được gọi để xác thực.

  • Sẵn sàng cho các thiết bị di động. Cookies là vấn để khi lưu trữ thông tin người dùng trong các ứng dụng di động. Việc sử dụng token đã đơn giản hóa đáng kể quá trình này.

  • CSRF (Cross Site Request Forgery) Bởi vì ứng dụng không dựa vào cookie để xác thực nên nó không thể bị tấn công bởi bởi kĩ thuật CSRF. (kỹ thuật tấn công bằng cách sử dụng quyền chứng thực của người dùng đối với một website)

  • Hiệu xuất Về mặt tải phía máy chủ việc tìm kiếm thông tin người dùng trong CSDL bằng thông tin người dùng gửi lên thì việc xác thực token sẽ tốn ít thời gian hơn. Điều này làm cho việc xác thực bằng token nhanh hơn so với phương pháp truyền thống.

IV. Token authentication hoạt động như thế nào ?

Cách thức xác thực dựa trên mã thông báo rất đơn giản. Người dùng nhập thông tin đăng nhập của mình và gửi yêu cầu đến máy chủ. Nếu thông tin đăng nhập là chính xác, máy chủ sẽ tạo token được mã hóa bằng thuật toán được quy đinh sẵn VD: HMACSHA256, còn được gọi là JSON web token (JWT). Máy khách lưu trữ JWT và thực hiện tất cả các yêu cầu tiếp theo đến máy chủ có token được đính kèm. Máy chủ xác thực người dùng bằng cách so sánh JWT được gửi với yêu cầu với yêu cầu mà nó đã lưu trong cơ sở dữ liệu. Đây là một sơ đồ đơn giản của quá trình:

V. JWT token chứa thông tin gì ?

Header 😱😱😱 Bao gồm loại mã thông báo (JWT) và loại thuật toán mã hóa (HS256) được mã hóa trong cơ sở-64.

Payload 😍😍😍 Chứa thông tin về người dùng và vai trò của người đó. Ví dụ: payload của token có thể chứa e-mail và mật khẩu.

Signature 💥💥💥 Chữ ký là một khóa duy nhất xác định dịch vụ tạo tiêu đề. Trong trường hợp này, chữ ký của mã thông báo sẽ là phiên bản được mã hóa cơ sở 64 của khóa bí mật của ứng dụng Rails ( Rails.application.secrets.secret_key_base). Vì mỗi ứng dụng có một khóa cơ sở duy nhất, khóa bí mật này đóng vai trò là chữ ký của token.

VI. Sử dụng token authentication với Rails 5.

Đã đến lúc thực hành. Bước đầu tiên là tạo một ứng dụng chỉ API Rails 5 mới:

rails new api-app --api

Bằng cách nối thêm --api vào cuối trình tạo, một ứng dụng only-API sẽ được tạo. Các ứng dụng chỉ dành cho API là những bổ sung gần đây cho nền tảng Rails. 😗

Có một số yêu cầu cần phải được đáp ứng trước khi chúng tôi có thể sử dụng phương pháp dự trên token:

  • Một cách mã hóa và giải mã mã thông báo JWT phải được thực hiện.
  • Chúng ta cần các phương pháp để kiểm tra nếu người dùng được xác thực.
  • Chúng ta cần có model
  • Controller để tạo và đăng nhập người dùng.
  • Chúng ta cần các route để điều hướng cho dứng dụng.

1. Tạo User Model 😁

rails g model User name email password_digest

chạy migrations.

 rails db:migrate

Bằng cách chạy các phương thức này, chúng ta đã tạo ra model User với các trường tên, e-mail và mật khẩu trong cơ sở dữ liệu. Phương thức has_secure_password phải được thêm vào mô hình để đảm bảo mật khẩu được mã hóa chính xác vào cơ sở dữ liệu. Has_secure_password là một phần của bcrypt ruby, vì vậy chúng ta phải cài đặt nó trước.

Thêm nó vào gemfile:

#Gemfile.rb
gem 'bcrypt', '~> 3.1.7'

cài đặt nó

 bundle install

Với gem đã được cài đặt, thêm phương thức has_secure_password vào trong model User:

#app/models/user.rb

class User < ApplicationRecord
 has_secure_password
end

2. Mã hóa và giải mã JWT token Khi mô hình người dùng được thực hiện, việc triển khai tạo JWT token có thể bắt đầu. Gem jwt sẽ giúp mã hóa và giải mã token sử dụng thuật toán HMACSHA256 có sẵn trong ứng dụng Rails.

Thêm gem jwt.

gem 'jwt'

cài đặt nó

bundle install

Khi gem được cài đặt, nó có thể được truy cập thông qua biến toàn cục JWT. Bởi vì các phương thức sẽ được sử dụng để yêu cầu đóng gói.

Khởi tạo một class JsonWebToken chứa singleton class để mã hóa và giải mã token.

# lib/json_web_token.rb

class JsonWebToken
 class << self
   def encode(payload, exp = 24.hours.from_now)
     payload[:exp] = exp.to_i
     JWT.encode(payload, Rails.application.secrets.secret_key_base)
   end

   def decode(token)
     body = JWT.decode(token, Rails.application.secrets.secret_key_base)[0]
     HashWithIndifferentAccess.new body
   rescue
     nil
   end
 end
end

Phương thức đầu tiên: encode, lấy ba tham số: ID người dùng, thời gian hết hạn (1 ngày) và khóa cơ sở duy nhất của ứng dụng Rails của bạn để tạo mã thông báo duy nhất. Phương thức thứ hai: decode, lấy token và sử dụng khóa bí mật của ứng dụng để giải mã nó.

Hai trường hợp mà các phương thức này sẽ được sử dụng:

  • Để xác thực người dùng và tạo mã thông báo cho người dùng bằng cách sử dụng encode.
  • Để kiểm tra xem token của người dùng được thêm vào trong mỗi yêu cầu có chính xác hay không bằng cách sử dụng decode.

Để đảm bảo mọi thứ sẽ hoạt động, nội dung của thư mục lib phải được đưa vào khi tải ứng dụng Rails.

#config/application.rb
module ApiApp
  class Application < Rails::Application
    #.....
    config.autoload_paths << Rails.root.join('lib')
    #.....
    end
  end
end

3. Xác thực người dùng. Thay vì sử dụng các phương pháp điều khiển riêng, simple_command đã được sử dụng. Để biết thêm thông tin về cài đặt hãy xem bài viết Simple_command . Thêm chung vào gem file.

gem 'simple_command'

cài đặt nó

bundle install

Sau đó, các phương thức alias của simple_command có thể dễ dàng được sử dụng trong một lớp bằng cách thêm prepend SimpleCommand. vào class AuthenticateUser

class AuthenticateUser
   prepend SimpleCommand

  def initialize()
   #this is where parameters are taken when the command is called
  end

  def call
   #this is where the result gets returned
  end

end

Lệnh lấy e-mail và mật khẩu của người dùng sau đó trả về người dùng, nếu thông tin đăng nhập khớp.

# app/commands/authenticate_user.rb

class AuthenticateUser
  prepend SimpleCommand

  def initialize(email, password)
    @email = email
    @password = password
  end

  def call
    JsonWebToken.encode(user_id: user.id) if user
  end

  private

  attr_accessor :email, :password

  def user
    user = User.find_by_email(email)
    return user if user && user.authenticate(password)

    errors.add :user_authentication, 'invalid credentials'
    nil
  end
end

Lệnh lấy các tham số và khởi tạo một thể hiện của lớp với email và password các thuộc tính có thể truy cập được trong lớp. private method user sử dụng thông tin đăng nhập để kiểm tra xem người dùng có tồn tại trong cơ sở dữ liệu hay không bằng User.find_by_email. Nếu người dùng được tìm thấy, sử dụng kèm phương thức authenticate. Phương thức này đã được tích hợp bằng cách cài đặt gem has_secure_password. Nó dùng để kiểm tra xem mật khẩu của người dùng có đúng không. Nếu mọi thứ là đúng, thông tin user sẽ được trả về. Nếu không, phương thức sẽ trả về nil. 💥👍

4 Kiểm tra ủy quyền người dùng. Việc tạo token đã được thực hiện, nhưng không có cách nào để kiểm tra xem mã thông báo đã được thêm vào yêu cầu có hợp lệ hay không. Lệnh ủy quyền phải lấy headers và yêu cầu và giải mã mã thông báo bằng phương thức decode trong singleton class JsonWebToken.

# app/commands/authorize_api_request.rb

class AuthorizeApiRequest
  prepend SimpleCommand

  def initialize(headers = {})
    @headers = headers
  end

  def call
    user
  end

  private

  attr_reader :headers

  def user
    @user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
    @user || errors.add(:token, 'Invalid token') && nil
  end

  def decoded_auth_token
    @decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
  end

  def http_auth_header
    if headers['Authorization'].present?
      return headers['Authorization'].split(' ').last
    else
      errors.add(:token, 'Missing token')
    end
    nil
  end
end

Giải thích từng method trong class. 👏👏👏

  • method http_auth_header lấy token từ headers khi class được khởi tạo.
  • method decoding_auth-token giải mã token nhận được từ http_auth_header và lấy ID người dùng.
  • method user ta sẽ đi qua từng dòng một.
  • Trong dòng đầu tiên, Về cơ bản, nếu User.find() trả về một tập hợp trống hoặc decoded_auth_token trả về false thì @usersẽ là nil.
  • Dòng thứ hai, phương thức user sẽ trả về user hoặc đưa ra lỗi. Trong Ruby, dòng cuối cùng của hàm được trả về hoàn toàn, do đó lệnh kết thúc trả về đối tượng người dùng.

5 Thực hiện các Helper Methods trong Controllers Tất cả logic để xử lý token JWT đã được đặt ra. Đã đến lúc triển khai nó trong Controller và đưa nó vào sử dụng thực tế. Hai phần thiết yếu nhất để thực hiện là xác định người dùng đăng nhập và tham chiếu người dùng hiện tại.

  • Login Users Đầu tiên, hãy bắt đầu với thông tin đăng nhập của người dùng:
# app/controllers/authentication_controller.rb

class AuthenticationController < ApplicationController
 skip_before_action :authenticate_request

 def authenticate
   command = AuthenticateUser.call(params[:email], params[:password])

   if command.success?
     render json: { auth_token: command.result }
   else
     render json: { error: command.errors }, status: :unauthorized
   end
 end
end

Method authen sẽ lấy thông tin người dùng sau đó xác thực nếu kết quả thành công sẽ trả về token để gọi các hành động sau này. Ngược lại nếu không thành công sẽ trả về mã lỗi.

  • Authorizing Requests phải có một phương thức current_user để duy trỳ đăng nhập của người dùng. Để sử dụng nó bạn phải được** khai báo** trong ApplicationController:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::API
 before_action :authenticate_request
  attr_reader :current_user

  private

  def authenticate_request
    @current_user = AuthorizeApiRequest.call(request.headers).result
    render json: { error: 'Not Authorized' }, status: 401 unless @current_user
  end
end

5 Nó có hoạt động không ? Hãy xem mọi thứ hoạt động như thế nào. Mở console trong ứng dụng của bạn.

rails c

Tạo thông tin người dùng trên rails console.

User.create!(email: 'example@mail.com' , password: '123123123' , password_confirmation: '123123123')

Để xem cách ủy quyền hoạt động, cần phải có một resouce để gửi yêu cầu tới. Mở console trên ứng dụng của bạn.

rails g scaffold Item name:string description:text

và chạy 

rails db:migrate

Thực hiện gửi yêu cầu đăng nhập

curl -H "Content-Type: application/json" -X POST -d '{"email":"example@mail.com","password":"123123123"}' http://localhost:3000/authenticate

Kết quả trả về là token sử dụng cho các lần gửi request sau.

{"auth_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE0NjA2NTgxODZ9.xsSwcPC22IR71OBv6bU_OGCSyfE89DvEzWfDU0iybMA"}

Chạy thử

curl http://localhost:3000/items
{"error":"Not Authorized"}

Một mã lỗi được trả về vì token không tồn tại trong header của bạn. Thêm token vào và chạy lại.

curl -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE0NjA2NTgxODZ9.xsSwcPC22IR71OBv6bU_OGCSyfE89DvEzWfDU0iybMA" http://localhost:3000/items

Một mảng trống được trả về có nghĩa là bạn đã thành công! Ứng dụng đã hoạt động. =)) Bạn có thể tham khảo ứng dụng tại đây

VI. Tài liệu tham khảo.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí