Xây dựng Authentication provider với gem OPro

I. Mở đầu

Xin chào các bạn (honho)

Chắc hẳn các bạn đã từng sử dụng tới OAuth2 để login vào trang web bằng tài khoản Facebook hoặc Google. (để biết chi tiết hơn, các bạn hãy đọc ở đây (yaoming))

Đại loại nó là phương thức chứng thực, cho phép các ứng dụng của bên thứ ba có quyền truy cập tới một HTTP Service, thay mặt chủ sở hữu sử dụng tài nguyên của mình (tất nhiên cần phải có sự cho phép của người đó). Yêu cầu truy cập có thế đến từ website, mobile app,...

Ở bài viết trước, tôi có demo một app nho nhỏ, dùng OAuth2 để login bằng tài khoản Twitter.

oauth_flow.png

theo mô hình trên, có thể hiểu phần code đó là ở phía bên trái hình - Application(client).

Hôm nay tôi sẽ tạo một App có chức năng giống như phần phía bên phải hình - Service API, với sự hỗ trợ của gem OProDevise.

Tôi sẽ tạo ra một Server để lưu trữ các thông tin của User, cung cấp các token để xác thực quyền truy cập.

Cùng với đó là một Client để tạo request yêu cầu truy cập và lấy thông tin tới Server ta vừa tạo. (dance2)

II. Demo

1. Workflow

- Bước 1: Register application

Giống như sử dụng OAuth để login thông qua các ông lớn khác (Facebook, google), ta cũng có công đoạn đăng ký thông tin.

Sau khi sign_up bằng gem Devise, OPro sẽ dẫn tới trang đăng ký.

Sau khi đăng ký thành công, server sẽ trả về cho chúng ta key là: Client idSecret.

Ta sử dụng key được cấp để build app client.

VD:

|*Name:*| Opro_client_test|
|*Client_id:*| abcdef123456789|
|*Secret:*| 987654321fedcba|

- Bước 2: User tạo request yêu cầu truy cập tới server

Từ Client app, muốn truy cập tới user account trên server, ta login và truy cập tới url sau:

http://domain/oauth/new?client_id=abcdef123456789&client_secret=987654321fedcba&redirect_uri=%2F

Url này sẽ dẫn tới page 'hỏi bạn có muốn cấp quyền truy cập không' ?

Nếu đồng ý, sẽ được redirect về trang ban đầu có kèm theo param code trên url, params được sử dụng để lấy access_token

http://domain/?code=qwerty123456

- Bước 3: Get access token

Có được đủ các params client_id, client_secret, code, ta tạo request tới server bằng POST.

http://domain/oauth/token.json?client_id=abcdef123456789&client_secret=987654321fedcba&code=qwerty123456

Response trả về dưới dạng json

{"access_token":"accesstoken123123123","refresh_token":"refreshtoken321654","expires_in":null}

- Bước 4: Sử dụng access token

Có được access_token cũng giống như việc ta đã được cấp cho 1 chiếc chìa khóa.

Dựa vào nó, có thể truy cập và lấy về dữ liệu của tài khoản đặt trên server.

Truy cập url như sau:

http://domain/oauth_tests/show_me_the_money.json?access_token=accesstoken123123123

Server sẽ trả về successful result

Các công cụ sử dụng:

  • Rails 4.2.1
  • MySQL2 0.3.18
  • Ruby 2.1.5

Let's start (honho)

2. Khởi tạo Server

Bắt đầu từ phần server trước, khởi tạo web application

rails new oproserver

Add thêm gem cần thiết

gem "opro"
gem "devise"

Gem Devise được add vào để giải quyết công đoạn đăng ký app và check quyền. Tất nhiên ta có thể tự check bằng tay nhưng ... tội gì không dùng (yaoming)

Sau khi chạy bundle install xong, tiến hành generate cho gem devise

rails g devise:install
rails g devise User

Tiến hành generate các file cho gem opro

rails g opro:install

Khi generate cho gem opro, ngoài file config và routes, nó sẽ generate ra thêm 2 file migrate table

Table opro_auth_grants: lưu trữ các token, expires time, permission.

Table opro_client_apps: lưu trữ các api key cung cấp phía app client sử dụng.

Sau khi có đủ các table cơ bản, ta chạy lệnh

rake db:migrate

Config xong gem, giờ xử lý view và controller.

Trước khi thiết lập layout, ta thêm bootstrap cho đẹp trai (yaoming)

gem "bootstrap-sass"

Nhớ import

# stylesheets/application.scss
@import "bootstrap-sprockets";
@import "bootstrap";
@import 'bootstrap/theme';

Xây dựng layout bên ngoài

# views/layouts/application.html.erb
<nav class="nav navbar-inverse">
  <div class="container" >
    <div class="navbar-header">
      <%= link_to "Authentication", root_path, class: "navbar-brand" %>
    </div>
    <div id="navbar">
      <ul class="nav navbar-nav">
      </ul>
    </div>
  </div>
</nav>

<div class="container">
  <% flash.each do |message_type, message| %>
    <div class="alert alert-<%= message_type %>">
      <%= message %>
    </div>
  <% end %>

  <%= yield %>

</div>

Tạo controller và view để điều hướng tới trang đăng ký API key

rails g controller pages index
# views/pages/index.html.erb

<h1> Welcome Kappa </h1>
<%= link_to 'Register client app', new_oauth_client_app_path %>

Kết quả

Trang chủ

index_server.png

Sau khi ấn vào link "Register client app", browser sẽ redirect tới trang để login và sign_up của devise tạo ra. Ta sign_up như bình thường

sign_up.png

Sign up hoàn tất, thông tin về user sẽ được lưu vào DB. Tiếp theo, ta sẽ được chuyển đến trang để đăng ký Oauth client application

create_client_app.png

Sau khi đăng ký, server sẽ cung cấp client_idclient_secret key cho bên phía client app sử dụng, ta nhớ lưu lại

response_api_key.png

Về cơ bản phía server đã xong (dance2)

3. Khởi tạo Client

Tạo web app cho client

rails new oproclient

Như đã trình bày ở workflow bên trên, sau khi có được client_idsecret cấp từ server, ta bắt đầu tạo url để truy cập tới đúng đường dẫn, lấy về access_token

Vì url khá dài, nên ta đặt nó bên trong method ở application_controller.rb

private
def new_opro_token_path
  "#{ENV['opro_base_url']}/oauth/new?client_id=#{ENV['opro_client_id']}&client_secret=#{ENV['opro_client_secret']}&redirect_uri=%2F%2Flocalhost%3A3001%2Foauth%2Fcallback"
end

helper_method :new_opro_token_path

Trong đó các biến env

  • ENV["opro_base_url"]: Chứa domain của server (ở trường hợp đang test là localhost:3000)

  • ENV["opro_client_id"]: Chứa client_id mà server cấp cho ở mục trên.

  • ENV["opro_client_secret"]: Chứa client_secret mà server cấp cho ở mục trên

  • params redirect_uri: url dẫn tới method callback để xử lý dữ liệu server trả về cho client (ở trường hợp này là localhost:3001/oauth/callback)

Tạo views cho trang index

<h1>Welcome!</h1>
<%= link_to "Authenticate via oPRO", new_opro_token_path %>

Khi truy cập tới new_opro_token_path bên phía server sẽ trả về params code theo url redirect

http://localhost:3001/?code=qwewqeqwe

Có được code rồi, ta sẽ có đủ điều kiện để lấy access_token trên server.

Tạo controller tên SessionsController để xử lý việc này

rails g controller sessions

Thêm routes tương ứng với callback

get "/oauth/callback", to: "sessions#create"

Vì request gửi lên server để lấy access_token phải ở dạng POST, cho nên ta add thêm gem rest-client để giúp xử lý việc này dễ dàng.

gem "rest-client"
# sessions_controller.rb
class SessionsController < ApplicationController
  def create
    response = JSON.parse RestClient.post("#{ENV['opro_base_url']}/oauth/token.json",
                    {
                        client_id: ENV['opro_client_id'],
                        client_secret: ENV['opro_client_secret'],
                        code: params[:code]
                    },
                    accept: :json)
    session[:access_token] = response['access_token']
    session[:refresh_token] = response['refresh_token']
    redirect_to root_path
  end
end

Với việc sử dụng RestClient, ta dễ dàng gửi một POST request tới server theo url

http://localhost:3000/oauth/token.json

cùng với 3 params: client_id, client_secretcode

access_token trả về được lưu vào session[:access_token] (dance2)

4. Sample API request

Cái quan trọng nhất là access_token đã lấy được, giờ ta tiếp tục tạo thử một request đính kèm access_token đến server xem đã được chấp thuận chưa.

Tạo một ApiTestsController với nội dung như sau:

class ApiTestsController < ApplicationController
  def index
    redirect_to root_path and return unless session[:access_token]
    @response = JSON.parse RestClient.get("#{ENV['opro_base_url']}/oauth_tests/show_me_the_money.json",
                                          params: {
                                              access_token: session[:access_token]
                                          },
                                          accept: :json)
  end
end

Add thêm routes

resourses :api_tests, only: :index

Vì phía server chưa thiết lập trả về nội dung gì cụ thể, nên ở view ta chỉ cho hiển thị messages response.

# views/api_tests/index.html.erb

<%= @response['message'] %>

OK, done.

Giờ ta chạy rails server cho oproserver ở port 3000, cho oproclient ở port 3001.

Truy cập vào trang chủ của client ( locahost:3001 ) ta được như sau.

index_client.png

Click vào link, nó sẽ dẫn ta tới trang hỏi kiểu 'hỏi bạn có muốn cấp quyền truy cập không' ?

permission_client.png

Sau khi ấn đồng ý, nó sẽ redirect về trang chủ.

access_token đã lấy được, ta thử truy cập vào url api_tests xem sao.

response_client.png

Vì bên phía server ta chưa thiết lập trả về nội dung gì, nên phía client chỉ hiển thị vỏn vẹn 1 messages xác nhận rằng đã access thành công tới server và được response dữ liệu.

GGWP! (dance2)

To be continue!!

Phần tiếp theo sẽ thiết lập thông tin User phía bên server, và response lại cho Client khi có request

Github

Client: https://github.com/NguyenTanDuc/opro_client

Server: https://github.com/NguyenTanDuc/opro_server

Nguồn tham khảo

https://github.com/opro/opro

https://viblo.asia/tungshooter/posts/3OEqGjDpR9bL

http://www.sitepoint.com/authenticate-all-the-things-with-opro-the-basics/


All Rights Reserved