Dev With Ethan

Blog Lập trình cùng Ethan

OAuth: Dùng Omniauth để đăng nhập với các dịch vụ phổ biến

… tiếp theo bài viết OAuth: Các khái niệm cơ bản

Chúng ta đã biết sử dụng OAuth có thể dùng để đăng nhập - xác thực và lấy thông tin từ những dịch vụ cung cấp OAuth. Trong bài này, tôi sẽ sử dụng RubySinatra cùng các gem Omniauth để thực hiện các thao tác.

1. Chuẩn bị

Chúng ta sẽ tạo 1 thư mục oauth-demo chứa project của chúng ta, tạo các file .ruby-version .ruby-gemset và cài đặt bundle gem:

1
2
3
4
5
6
7
8
9
$ mkdir oauth-demo && cd "$_"
$ echo 2.2.4 > .ruby-version
$ echo oauth-demo > .ruby-gemset
$ cd .
ruby-2.2.4 - #gemset created /Users/ethan605/.rvm/gems/ruby-2.2.4@oauth-demo
ruby-2.2.4 - #generating oauth-demo wrappers - please wait
$ gem install bundle
$ bundle init
Writing new Gemfile to /Users/ethan605/Documents/Workspaces/Ruby/oauth-demo/Gemfile
Shell snippet 1
Chuẩn bị Ruby & bundle

Lúc này, bundle sẽ tạo ra 1 file tên là Gemfile, ta thêm vào file này các gem như sau:

1
2
3
4
5
6
7
8
# frozen_string_literal: true
source "https://rubygems.org"

gem 'sinatra', '~> 1.4', '>= 1.4.7'
gem 'sinatra-reloader', '~> 1.0'

# Strategies
gem 'omniauth', '~> 1.3', '>= 1.3.1'
Ruby code snippet 1
Chuẩn bị Ruby gems

Ở đây, omniauth sẽ giúp chúng ta sử dụng được nhiều OAuth strategies khác nhau.

Rồi chạy lệnh bundle install để cài đặt:

1
$ bundle install
Shell snippet 2
Install bundle

Mặc định Sinatra chỉ cần 1 file Ruby bất kỳ được cài đặt theo chuẩn. Tuy nhiên trong ví dụ này chúng ta sẽ sử dụng Rack middleware để quản lý các cài đặt 1 cách tập trung hơn.

Theo cách này, project của chúng ta cần 3 file: config.ru, secrets.yml & app.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# config.ru
require 'bundler/setup'
require 'sinatra/config_file'
require 'omniauth-facebook'
require './app.rb'

config_file './secrets.yml'

use Rack::Session::Cookie, secret: settings.cookies["secret"]

use OmniAuth::Builder do
  # For multiple strategies configs
end

run Sinatra::Application
Ruby code snippet 2
Cấu hình file config.ru
1
2
3
# secrets.yml
cookies:
  secret: "oauth-demo@dev.ethanify.me"
Yaml snippet 1
Cấu hình file secrets.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# app.rb
require 'sinatra'
require 'sinatra/reloader'

# configure sinatra
set :run, false
set :raise_errors, true

get '/login/:provider' do
  # For OAuth login requests
end

get '/auth/:provider/callback' do
  # For OAuth login callbacks
end
Ruby code snippet 3
Cấu hình file app.rb

Trong đó, mỗi file có 1 nhiệm vụ:

  • config.ru: chứa toàn bộ cài đặt của project Sinatra để đảm bảo chạy đúng
  • secrets.yml: chứa các thông tin quan trọng và nhạy cảm (Secret của Cookies, App ID, App Secret của các dịch vụ mạng xã hội cung cấp đăng nhập & xác thực bằng OAuth)
  • app.rb: chứa các xử lý logic đối với từng request từ phía client.

Trong ví dụ này chúng ta có 2 request:

  • GET đến đường dẫn /login/oauth, ở đây :provider có thể là facebook, google, twitter,…, dùng để hiện ra 1 trang web, nơi mà người dùng có thể đăng nhập bằng Facebook, Google+ hay Twitter,…
  • GET đến đường dẫn /auth/:provider/callback, đây là đường dẫn sẽ được xử lý tự động bằng Omniauth.

Cuối cùng, ta tiến hành chạy server bằng lệnh rackup tại thư mục có chứa file config.ru:

1
2
3
4
5
$ rackup
loading config file './secrets.yml'
[2016-07-13 15:54:40] INFO  WEBrick 1.3.1
[2016-07-13 15:54:40] INFO  ruby 2.2.4 (2015-12-16) [x86_64-darwin15]
[2016-07-13 15:54:40] INFO  WEBrick::HTTPServer#start: pid=5925 port=9292
Shell snippet 3
Chạy rackup

Lúc này, server của chúng ta sẽ nhận các request thông qua địa chỉ http://localhost:9292.

2. Đăng nhập với Facebook

2.1. Cài đặt Facebook

Đối với tất cả các dịch vụ cung cấp OAuth hiện nay, chúng ta đều cần phải đăng ký trở thành developer và đăng ký ứng dụng (gọi là app) với các dịch vụ này. Đối với Facebook, chúng ta thực hiện đăng ký qua trang developers.facebook.com.

Sau khi tạo mới 1 ứng dụng, tại Dashboard, các bạn click vào + Add Product ở panel bên tay trái như trong hình dưới đây:

Facebook Settings - Add Product
Add Product

Trong màn hình Product Setup, ta chọn Get Started tại mục Facebook Login:

Facebook Settings - Facebook Login - Get Started
Facebook Login - Get Started

Tại phần Valid OAuth redirect URIs ta đặt giá trị localhost:9292/auth/facebook/callback:

Facebook Settings - Facebook Login - Valid OAuth redirect URIs
Facebook Login - Valid OAuth redirect URIs

Các cài đặt này đảm bảo Facebook nhận diện đúng OAuth từ server của chúng ta, tránh trường hợp nhận diện nhầm các server khác, từ đó có thể bị tấn công và ăn cắp dữ liệu người dùng.

2.2. Cài đặt server OAuth Demo

Quay lại với project OAuth Demo, ta sẽ sử dụng gem omniauth-facebook để thực hiện các thao tác OAuth với Facebook:

1
2
3
# Gemfile
gem 'omniauth', '~> 1.3', '>= 1.3.1'
gem 'omniauth-facebook', '~> 3.0'
Ruby code snippet 4
Thêm gem Omniauth cho Facebook

Tại file secrets.yml, ta đặt các giá trị App IDApp Secret lấy từ trang Dashboard của Facebook:

Facebook Settings - App ID & App Secret
App ID & App Secret
1
2
3
4
# secrets.yml
facebook:
  app_id: "1058494070XXXXXX"
  app_secret: "27e352595e6effa0db8d44870dXXXXXX"
Yaml snippet 2
Cài đặt Facebook App ID & App secret

2.3. Đăng nhập với Facebook

Thông thường, Sinatra cho phép trả về nội dung HTML ngay trong hàm xử lý request của mình. Tuy nhiên chúng ta sẽ sử dụng các file .html để tiện quản lý hơn. Ta đặt 1 file tên là facebook.html trong thư mục views/ và trả về trong request login/:provider:

1
2
3
4
get '/login/:provider' do
  content_type 'text/html'
  send_file "views/#{params[:provider]}.html"
end
Ruby code snippet 5
Cấu hình request /login/:provider

Ở đây, :provider là 1 symbol, có nghĩa là nó sẽ tượng trưng cho 1 String truyền vào sau /login/, và được gọi thông qua params[:provider]. Như vậy, nếu đối với mỗi provider, chúng ta có 1 file có dạng <provider>.html nằm trong thư mục views/ thì ta có thể trả về và hiển thị trong request /login/:provider

Trong views/facebook.html ta cài đặt Facebook Javascript SDK như trong hướng dẫn ở trang developers.facebook.com và bắt sự kiện click vào nút Đăng nhập với Facebook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$('a').click(function(e) {
  FB.login(function(response) {
    if (response.authResponse) {
      $('#connect').html('Kết nối thành công! Gọi callback GET /auth/facebook/callback...');
      
      $.getJSON('/auth/facebook/callback', function(user) {
        $('#connect').html('Xác thực thành công! Kết thúc quá trình đăng nhập.');

        $('#uid').html(user.uid)
        $('#display-name').html(user.info.name)
        $('#profile-image').attr('src', user.info.image)
        $('#access-token').html(user.credentials.token)

        $('#results').show(500)
      });
    }
  });
});
Javascript code snippet 1
Cài đặt Facebook JS SDK & bắt sự kiện Đăng nhập Facebook

Ở đây, chúng ta gọi FB.login() để mở màn hình đăng nhập của Facebook, khi nhận được response, ta kiểm tra response.authResponse có tồn tại không, nếu có, ta sẽ gọi tiếp /auth/facebook/callback để cho Omniauth xử lý các dữ liệu trả về.

Lưu ý: ở đây chúng ta đang sử dụng cookies để truyền authorization code nên chúng ta sẽ không thấy params nào trong request /auth/facebook/callback. Để Facebook Javascript SDK trả về authorization code trong cookies, ta cần bật cookie: true trong cài đặt của SDK:

1
2
3
4
5
6
FB.init({
  appId      : '1058494070XXXXXX',
  xfbml      : true,
  version    : 'v2.7',
  cookie     : true
});
Javascript code snippet 2
Bật cookie trong Facebook JS SDK

Chạy Sinatra server, chúng ta nhận được kết quả như sau:

Khi click vào Đăng nhập với Facebook, một màn hình đăng nhập của Facebook hiện ra (đối với những tài khoản đã đăng nhập thì màn hình này sẽ không hiện ra nữa):

Facebook OAuth - Login to Facebook
Login to Facebook

Sau khi đăng nhập, người dùng được chọn xem có đồng ý để ứng dụng OAuth Demo truy cập và sử dụng các thông tin cá nhân hay không:

Facebook OAuth - Review permissions
Review permissions

Cuối cùng, sau khi người dùng đồng ý, OAuth Demo nhận được các thông tin cơ bản nhất (User ID, tên, ảnh avatar, access token):

Facebook OAuth - Results
Results

3. Đăng nhập với Google+

3.1. Cài đặt Google+

Nhìn chung, hiện tại Google đang có quá nhiều dịch vụ & API khác nhau cho lập trình viên sử dụng, nên việc cài đặt 1 ứng dụng tại Google cũng khá phức tạp. Tại trang console.developers.google.com, các bạn tạo mới 1 project và thêm Google+ API theo các bước lần lượt như sau:

Tìm đến Google+ API trong danh sách các API khả dụng:

Google Settings - Find Google+ API
Find Google+ API

Chọn Enable trong trang Google+ API:

Google Settings - Enable Google+ API
Enable Google+ API

Chuyển đến trang tạo Credentials sau khi Enable API:

Google Settings - Go to Credentials
Go to Credentials

Tại trang Credentials, chọn Google+ API cho Web browser (Javascript) ** & truy cập vào **User data:

Google Settings - Determine Credentials
Determine Credentials

Tương tự như Facebook, chúng ta cũng phải cung cấp Authorized redirect URIs, nhưng với Google, chúng ta cần khai báo cả Authorized Javascript origins:

Google Settings - OAuth 2.0 client ID
OAuth 2.0 client ID

Tiếp đó, ta được chuyển tới cài đặt màn hình thông tin khi đăng nhập, các bạn tự cài đặt bước này, cuối cùng, chúng ta có thể download 1 file client_id.json chứa toàn bộ nội dung các cài đặt vừa rồi:

Google Settings - Download credentials
Download credentials
1
2
3
4
5
6
7
8
9
10
11
12
{
  "web":{
    "client_id": "255460691335-h45t6k92hhu0nr6ejajtojm5a1XXXXXX.apps.googleusercontent.com",
    "project_id": "oauth-demo-ethanify",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://accounts.google.com/o/oauth2/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_secret": "-yayDVQCDFTSCuF9slXXXXXX",
    "redirect_uris": ["http://localhost:9292/oauth/google_oauth2/callback"],
    "javascript_origins": ["http://localhost:9292"]
  }
}
JSON snippet 1
Nội dung client_id.json trả về từ Google

3.2. Cài đặt server OAuth Demo

Giống với Facebook, để sử dụng OAuth2 với Google, chúng ta cần cài đặt gem omniauth-google-oauth2:

1
2
3
# Gemfile
gem 'omniauth', '~> 1.3', '>= 1.3.1'
gem 'omniauth-google-oauth2', '~> 0.4.1'
Ruby code snippet 6
Thêm gem Omniauth cho Google

Trong file secrets.yml, ta cũng thêm client_idclient_secret giống như Facebook:

1
2
3
4
# secrets.yml
google:
  client_id: "255460691335-h45t6k92hhu0nr6ejajtojm5a1XXXXXX.apps.googleusercontent.com"
  client_secret: "-yayDVQCDFTSCuF9slXXXXXX"
Yaml snippet 3
Cấu hình Google Client ID & Client secret

2 thông số này được cung cấp thông qua file client_id.json

Trong config.ru, ta thêm provider trong Omniauth::Builder:

1
2
3
4
use omniauth::builder do
  provider :facebook, settings.facebook["app_id"], settings.facebook["app_secret"]
  provider :google_oauth2, settings.google["client_id"], settings.google["client_secret"]
end
Ruby code snippet 7
Khai báo Google OAuth trong Omniauth Builder

Như vậy, bên cạnh provider đã hoàn thành trong phần trước là :facebook, chúng ta có thêm :google_oauth2. Ta sẽ tạo 1 file google_oauth2.html trong thư mục views để xử lý đăng nhập với Google+.

Khác với Facebook, chúng ta sẽ gọi trực tiếp đường dẫn /auth/google_oauth2 để chuyển tới màn hình đăng nhập của Google:

1
2
3
$('a').click(function(e) {
  window.open('/auth/google_oauth2');
});
Javascript code snippet 3
Chuyển tới màn hình đăng nhập Google

Cuối cùng, khi chạy server, chúng ta được kết quả như sau:

Khi click vào Đăng nhập với Google, ta chuyển sang trang đăng nhập với Google:

Google Settings - Login to Google
Login to Google

Đăng nhập xong, người dùng được hỏi có đồng ý cung cấp thông tin cho ứng dụng OAuth Demo không:

Google Settings - Review permissions
Review permissions

Cuối cùng, Google trả về dữ liệu thông qua đường dẫn /auth/google_oauth2/callback, có dạng JSON:

Google Settings - Results
Results

Các bạn có thể tải về project OAuth Demo để cùng xem các ví dụ về OAuth đã trình bày trong bài.